7.2. インスタンスの宣言(Instance Declarations)🔗

インスタンス宣言の構文は定義とほとんど同じです。唯一の構文の違いは、キーワード Lean.Parser.Command.declaration : commanddefLean.Parser.Command.declaration : commandinstance に置き換えられていることと、名前が省略可能であることです:

syntax

ほとんどのインスタンスでは Lean.Parser.Command.declaration : commandwhere 構文を使って各メソッドを定義します:

instance ::= ...
    | `attrKind` matches `("scoped" <|> "local")?`, used before an attribute like `@[local simp]`. instance ((priority := prio))? `declId` matches `foo` or `foo.{u,v}`: an identifier possibly followed by a list of universe names declId? `declSig` matches the signature of a declaration with required type: a list of binders and then `: type` declSig where
        structInstField*

しかし、型クラスは帰納型であるため、インスタンスは適切な型を持つ任意の式を使って構築できます:

instance ::= ...
    | `attrKind` matches `("scoped" <|> "local")?`, used before an attribute like `@[local simp]`. instance ((priority := prio))? `declId` matches `foo` or `foo.{u,v}`: an identifier possibly followed by a list of universe names declId? `declSig` matches the signature of a declaration with required type: a list of binders and then `: type` declSig :=
        termTermination hints are `termination_by` and `decreasing_by`, in that order.

インスタンスはケースによって定義することもできます;しかし、 Decidable のインスタンス以外でこの機能が使われることはほとんどありません:

instance ::= ...
    | `attrKind` matches `("scoped" <|> "local")?`, used before an attribute like `@[local simp]`. instance ((priority := prio))? `declId` matches `foo` or `foo.{u,v}`: an identifier possibly followed by a list of universe names declId? `declSig` matches the signature of a declaration with required type: a list of binders and then `: type` declSig
        (| term => term)*Termination hints are `termination_by` and `decreasing_by`, in that order.

明示的な条件で定義されたインスタンスは多くの場合、メソッドの実装をラップする匿名コンストラクタ( Lean.Parser.Term.anonymousCtor : termThe *anonymous constructor* `⟨e, ...⟩` is equivalent to `c e ...` if the expected type is an inductive type with a single constructor `c`. If more terms are given than `c` has parameters, the remaining arguments are turned into a new anonymous constructor application. For example, `⟨a, b, c⟩ : α × (β × γ)` is equivalent to `⟨a, ⟨b, c⟩⟩`. ⟨...⟩ )か、定義が等しい型に対する inferInstanceAs の呼び出しで構成されます。

インスタンスのエラボレーションは以下に示す注意点を除いて通常の定義の作成とほとんど同じです。名前が提供されない場合は、自動的に名前が生成されます。この生成された名前を直接参照することは可能ですが、名前の生成に使用されるアルゴリズムは過去に変更されたことがあり、将来的にも変更される可能性があります。直接参照されるインスタンスには明示的に名前を付ける方が良いです。エラボレーションの後、新しいインスタンスはインスタンス検索の候補として登録されます。名前に instance 属性を追加することで、他の定義された名前を候補としてマークすることができます。

インスタンス名の生成

以下のこれらの定義について:

structure NatWrapper where val : Nat instance : BEq NatWrapper where beq | x, y => x == y

新しいインスタンスの名前は instBEqNatWrapper となります。

インスタンス定義のバリエーション

この構造体型に対して:

structure NatWrapper where val : Nat

BEq インスタンスの定義である以下のすべては同等です:

instance : BEq NatWrapper where beq | x, y => x == y instance : BEq NatWrapper := fun x y => x.val == y.val instance : BEq NatWrapper := fun x y => x == y

環境に異なる名前を導入することを除けば、以下の同等です:

@[instance] def instBeqNatWrapper : BEq NatWrapper where beq | x, y => x == y instance : BEq NatWrapper := fun x y => x.val == y.val instance : BEq NatWrapper := fun x y => x == y

7.2.1. 再帰的なインスタンス(Recursive Instances)🔗

Lean.Parser.Command.declaration : commandwhere による構造体定義構文で定義された関数は再帰的ではありません。インスタンス宣言は構造体定義の亜種であるため、型クラスのメソッドもデフォルトでは再帰的ではありません。しかし、再帰的帰納型のインスタンスは一般的です。この制限を回避するための標準的なパターンがあります:インスタンスとは独立に再帰関数を定義し、インスタンス定義でそれを参照するというものです。慣例として、これらの再帰関数は対応するメソッドの名前を持ちますが、型の名前空間で定義されます。

インスタンスは再帰的ではない

以下の NatTree の定義に対して:

inductive NatTree where | leaf | branch (left : NatTree) (val : Nat) (right : NatTree)

以下の BEq インスタンスは失敗します:

instance : BEq NatTree where beq | .leaf, .leaf => true | .branch l1 v1 r1, .branch l2 v2 r2 => failed to synthesize BEq NatTree Additional diagnostic information may be available using the `set_option diagnostics true` command.l1 == l2 && v1 == v2 && failed to synthesize BEq NatTree Additional diagnostic information may be available using the `set_option diagnostics true` command.r1 == r2 | _, _ => false

これは左右両方の再帰呼び出しでエラーが発生しています:

failed to synthesize
  BEq NatTree
Additional diagnostic information may be available using the `set_option diagnostics true` command.

NatTree.beq のような適切な再帰関数を与え:

def NatTree.beq : NatTree NatTree Bool | .leaf, .leaf => true | .branch l1 v1 r1, .branch l2 v2 r2 => l1 == l2 && v1 == v2 && r1 == r2 | _, _ => false

次のステップでインスタンスを作成できます:

instance : BEq NatTree where beq := NatTree.beq

もしくは同等ですが、匿名コンストラクタ構文を使うことでも作成できます:

instance : BEq NatTree := NatTree.beq

さらに、インスタンスはそれ自身の定義中はインスタンス統合のために利用できません。インスタンスは定義された後、はじめてインスタンス統合のために利用可能であるとマークされます。型の再帰的な出現が他の帰納型のパラメータとして出現している入れ子になった帰納型ではインスタンスは再帰関数としても書けることを要求する場合があります。この制限を回避する標準的なイディオムは、定義されている関数への参照を含む再帰的に定義された関数内にてローカルインスタンスを作成することです。これにはローカルコンテキストのすべての束縛においてインスタンス統合が正しい型で使える利点があります。

入れ子の型のインスタンス

この NatRoseTree の定義では、定義されている型は別の帰納型コンストラクタ( Array )の下に入れ子になっています:

inductive NatRoseTree where | node (val : Nat) (children : Array NatRoseTree)

バラの木の等価性をチェックするには、配列の等価性をチェックする必要があります。しかし、インスタンスは通常それ自身の定義中にインスタンス統合のために利用できないため、たとえ NatRoseTree.beq が再帰関数であり、それ自身の定義のスコープ内にあるにもかかわらず以下の定義は失敗します。

def NatRoseTree.beq : (tree1 tree2 : NatRoseTree) Bool | .node val1 children1, .node val2 children2 => val1 == val2 && failed to synthesize BEq (Array NatRoseTree) Additional diagnostic information may be available using the `set_option diagnostics true` command.children1 == children2
failed to synthesize
  BEq (Array NatRoseTree)
Additional diagnostic information may be available using the `set_option diagnostics true` command.

これを解決するために、let 束縛されたローカルの BEq NatRoseTree インスタンスを作成します:

partial def NatRoseTree.beq : (tree1 tree2 : NatRoseTree) Bool | .node val1 children1, .node val2 children2 => let _ : BEq NatRoseTree := NatRoseTree.beq val1 == val2 && children1 == children2

インスタンス統合時に、子に対して配列の等価性を使用することで let 束縛されたインスタンスが発見されて使用されます。

7.2.2. class inductive のインスタンス(Instances of class inductives)🔗

多くのインスタンスは関数型を持っています:それ自身がインスタンス検索を再帰的に呼び出す任意のインスタンスは関数であり、暗黙のパラメータを持つインスタンスも同様です。ほとんどのインスタンスは自身のインスタンスパラメータからメソッドの実装を射影するだけですが、帰納的なクラスの型のインスタンスは通常、1つ以上の引数をパターンマッチさせ、インスタンスが適切なコンストラクタを選択できるようにします。これは通常の Lean の関数の構文を使って行われます。他のインスタンスと同様に、問題の関数はそれ自身の定義ではインスタンス統合のために利用できません。

直和クラスのためのインスタンス

DecidableEq α(a b : α) Decidable (Eq a b) の省略形であるため、この例のように直接引数を使うことができます:

inductive ThreeChoices where | yes | no | maybe instance : DecidableEq ThreeChoices | .yes, .yes => .isTrue rfl | .no, .no => .isTrue rfl | .maybe, .maybe => .isTrue rfl | .yes, .maybe | .yes, .no | .maybe, .yes | .maybe, .no | .no, .yes | .no, .maybe => .isFalse nofun
直和クラスのための再帰的なインスタンス

StringList はモノ射な文字列リストを表します:

inductive StringList where | nil | cons (hd : String) (tl : StringList)

以下の DecidableEq インスタンスを定義しようとすると、内部の termIfThenElse : term`if c then t else e` is notation for `ite c t e`, "if-then-else", which decides to return `t` or `e` depending on whether `c` is true or false. The explicit argument `c : Prop` does not have any actual computational content, but there is an additional `[Decidable c]` argument synthesized by typeclass inference which actually determines how to evaluate `c` to true or false. Write `if h : c then t else e` instead for a "dependent if-then-else" `dite`, which allows `t`/`e` to use the fact that `c` is true/false. if をエラボレートする際にインスタンス統合が呼び出されますが、インスタンスがそれ自身の定義でインスタンス統合に利用できないため失敗します:

instance : DecidableEq StringList | .nil, .nil => .isTrue rfl | .cons h1 t1, .cons h2 t2 => if h : h1 = h2 then failed to synthesize Decidable (t1 = t2) Additional diagnostic information may be available using the `set_option diagnostics true` command.if h' : t1 = t2 then .isTrue (h1:Stringt1:StringListh2:Stringt2:StringListh:h1 = h2h':t1 = t2StringList.cons h1 t1 = StringList.cons h2 t2 All goals completed! 🐙) else .isFalse (h1:Stringt1:StringListh2:Stringt2:StringListh:h1 = h2h':¬t1 = t2¬StringList.cons h1 t1 = StringList.cons h2 t2 h1:Stringt1:StringListh2:Stringt2:StringListh:h1 = h2h':¬t1 = t2hEq:StringList.cons h1 t1 = StringList.cons h2 t2False; h1:Stringt1:StringListh:h1 = h1h':¬t1 = t1False; All goals completed! 🐙) else .isFalse (h1:Stringt1:StringListh2:Stringt2:StringListh:¬h1 = h2¬StringList.cons h1 t1 = StringList.cons h2 t2 h1:Stringt1:StringListh2:Stringt2:StringListh:¬h1 = h2hEq:StringList.cons h1 t1 = StringList.cons h2 t2False; h1:Stringt1:StringListh:¬h1 = h1False; All goals completed! 🐙) | .nil, .cons _ _ | .cons _ _, .nil => .isFalse nofun
failed to synthesize
  Decidable (t1 = t2)
Additional diagnostic information may be available using the `set_option diagnostics true` command.

しかし、これは通常の Lean の関数であるため、明示的に与えられた名前を再帰的に参照することができます:

instance instDecidableEqStringList : DecidableEq StringList | .nil, .nil => .isTrue rfl | .cons h1 t1, .cons h2 t2 => if h : h1 = h2 then if h' : application type mismatch @dite ?m.74 (instDecidableEqStringList t1 t2) argument instDecidableEqStringList t1 t2 has type Decidable (t1 = t2) : Type but is expected to have type Prop : TypeinstDecidableEqStringList t1 t2 then .isTrue (h1:Stringt1:StringListh2:Stringt2:StringListh:h1 = h2h':sorry `«Manual.Classes.InstanceDecls:442:14»StringList.cons h1 t1 = StringList.cons h2 t2 h1:Stringt1:StringListh2:Stringt2:StringListh:h1 = h2h':sorry `«Manual.Classes.InstanceDecls:442:14»t1 = t2) else .isFalse (h1:Stringt1:StringListh2:Stringt2:StringListh:h1 = h2h':¬sorry¬StringList.cons h1 t1 = StringList.cons h2 t2 h1:Stringt1:StringListh2:Stringt2:StringListh:h1 = h2h':¬sorryhEq:StringList.cons h1 t1 = StringList.cons h2 t2False; h1:Stringt1:StringListh':¬sorryh:h1 = h1False; h1:Stringt1:StringListh':¬sorryh:h1 = h1False) else .isFalse (h1:Stringt1:StringListh2:Stringt2:StringListh:¬h1 = h2¬StringList.cons h1 t1 = StringList.cons h2 t2 h1:Stringt1:StringListh2:Stringt2:StringListh:¬h1 = h2hEq:StringList.cons h1 t1 = StringList.cons h2 t2False; h1:Stringt1:StringListh:¬h1 = h1False; All goals completed! 🐙) | .nil, .cons _ _ | .cons _ _, .nil => .isFalse nofun

7.2.3. インスタンスの優先度(Instance Priorities)🔗

インスタンスには 優先度 (priority)を割り当てることができます。インスタンス統合において、高い優先度のインスタンスが優先されます:インスタンス統合の詳細については インスタンス統合の節 を参照してください。

syntax

優先度は数値を指定できます:

prio ::=
    num

優先度が指定されていない場合、 1000 に対応するデフォルトの優先度が使用されます:

prio ::= ...
    | The default priority `default = 1000`, which is used when no priority is set. default

数値による指定が細かすぎるケースのために、 100 ・ 500 ・ 10000 の名前付きの優先度を利用することができます。 prioMid : prioThe standardized "medium" priority `mid = 500`. This is lower than `default`, and higher than `low`. mid 優先度は prioDefault : prioThe default priority `default = 1000`, which is used when no priority is set. default よりも低くなります。

prio ::= ...
    | The standardized "low" priority `low = 100`, for things that should be lower than default priority. low
prio ::= ...
    | The standardized "medium" priority `mid = 500`. This is lower than `default`, and higher than `low`.
mid
prio ::= ...
    | The standardized "high" priority `high = 10000`, for things that should be higher than default priority. high

最後に、優先度は足し引きが可能であるため、default + 2 は有効な優先度であり、 1002 に対応します:

prio ::= ...
    | Parentheses are used for grouping priority expressions. (prio)
prio ::= ...
    | Addition of priorities. This is normally used only for offsetting, e.g. `default + 1`. prio + prio
prio ::= ...
    | Subtraction of priorities. This is normally used only for offsetting, e.g. `default - 1`. prio - prio

7.2.4. デフォルトインスタンス(Default Instances)🔗

default_instance 属性は、そのインスタンスが 他に選択するのに十分な情報がない場合にフォールバックとして使用する ことを指定します。優先度が指定されていない場合、デフォルトの優先度 default が使用されます。

syntax
attr ::= ...
    | default_instance prio?
デフォルトインスタンス

OfNat Nat のデフォルトインスタンスは、他の型情報がない場合に自然数リテラルに対して Nat を選択するために使用されます。これは Lean の標準ライブラリで優先度 100 で宣言されています。この偶数の表現では、偶数はその半分の値で表現されています:

structure Even where half : Nat

以下のインスタンスでは、数値リテラルを小さな Even に使用することができます(型クラスインスタンス検索の深さの制限により、任意の大きさのリテラルに使用することはできません):

instance ofNatEven0 : OfNat Even 0 where ofNat := 0 instance ofNatEvenPlusTwo [OfNat Even n] : OfNat Even (n + 2) where ofNat := (OfNat.ofNat n : Even).half + 1 { half := 0 }#eval (0 : Even) { half := 17 }#eval (34 : Even) { half := 127 }#eval (254 : Even)
{ half := 0 }
{ half := 17 }
{ half := 127 }

これらを優先度が 100 以上のデフォルトインスタンスとして指定すると、 Nat の代わりに使用されます:

attribute [default_instance 100] ofNatEven0 attribute [default_instance 100] ofNatEvenPlusTwo { half := 0 }#eval 0 { half := 17 }#eval 34
{ half := 0 }
{ half := 17 }

偶数でない数字では依然として OfNat Nat インスタンスが使用されます:

5#eval 5
5

7.2.5. インスタンス属性(The Instance Attribute)🔗

instance 属性は指定された優先度で名前をインスタンスであると宣言します。他の属性と同様に、 instance 属性はグローバル・ローカル・現在の名前空間がオープンされた時にのみに適用できます。 Lean.Parser.Command.declaration : commandinstance 宣言は instance 属性を自動的に適用する定義の形式です。

syntax

適用される定義がインスタンスであることを宣言します。優先度を指定しない場合は、デフォルトの優先度 default が使用されます。

attr ::= ...
    | instance prio?