Type classes are declared with the Lean.Parser.Command.declaration : commandclass keyword.
syntax
command::= ...
|`declModifiers` is the collection of modifiers on a declaration:
* a doc comment `/-- ... -/`
* a list of attributes `@[attr1, attr2]`
* a visibility specifier, `private` or `protected`
* `noncomputable`
* `unsafe`
* `partial` or `nonrec`
All modifiers are optional, and have to come in the listed order.
`nestedDeclModifiers` is the same as `declModifiers`, but attributes are printed
on the same line as the declaration. It is used for declarations nested inside other syntax,
such as inductive constructors, structure projections, and `let rec` / `where` definitions. declModifiersclass`declId` matches `foo` or `foo.{u,v}`: an identifier possibly followed by a list of universe names declIdbracketedBinder*(extendsterm,*)?(:term)?where(`declModifiers` is the collection of modifiers on a declaration:
* a doc comment `/-- ... -/`
* a list of attributes `@[attr1, attr2]`
* a visibility specifier, `private` or `protected`
* `noncomputable`
* `unsafe`
* `partial` or `nonrec`
All modifiers are optional, and have to come in the listed order.
`nestedDeclModifiers` is the same as `declModifiers`, but attributes are printed
on the same line as the declaration. It is used for declarations nested inside other syntax,
such as inductive constructors, structure projections, and `let rec` / `where` definitions. declModifiersident::)?structFields(derivingident,*)?
Declares a new type class.
The Lean.Parser.Command.declaration : commandclass declaration creates a new single-constructor inductive type, just as if the Lean.Parser.Command.declaration : commandstructure command had been used instead.
In fact, the results of the Lean.Parser.Command.declaration : commandclass and Lean.Parser.Command.declaration : commandstructure commands are almost identical, and features such as default values may be used the same way in both.
Please refer to the documentation for structures for more information about default values, inheritance, and other features of structures.
The differences between structure and class declarations are:
Methods instead of fields
Instead of creating field projections that take a value of the structure type as an explicit parameter, methods are created. Each method takes the corresponding instance as an instance-implicit parameter.
Instance-implicit parent classes
The constructor of a class that extends other classes takes its class parents' instances as instance-implicit parameters, rather than explicit parameters.
When instances of this class are defined, instance synthesis is used to find the values of inherited fields.
Parents that are not classes are still explicit parameters to the underlying constructor.
Parent projections via instance synthesis
Structure field projections make use of inheritance information to project parent structure fields from child structure values.
Classes instead use instance synthesis: given a child class instance, synthesis will construct the parent; thus, methods are not added to child classes in the same way that projections are added to child structures.
Registered as class
The resulting inductive type is registered as a type class, for which instances may be defined and that may be used as the type of instance-implicit arguments.
Out and semi-out parameters are considered
The outParam and semiOutParamgadgets have no meaning in structure definitions, but they are used in class definitions to control instance search.
While Lean.Parser.Command.declaration : commandderiving clauses are allowed for class definitions to maintain the parallel between class and structure elaboration, they are not frequently used and should be considered an advanced feature.
No Instances of Non-Classes
Lean rejects instance-implicit parameters of types that are not classes:
deff[n:invalid binder annotation, type is not a class instance
Nat
use the command `set_option checkBinderAnnotations false` to disable the checkNat]:n=n:=rfl
invalid binder annotation, type is not a class instance
Nat
use the command `set_option checkBinderAnnotations false` to disable the check
Finally, C2.Monoid.mk takes its semigroup parent as an instance implicit parameter.
The references to op become references to the method C2.Magma.op, relying on instance synthesis to recover the implementation from the C2.Semigroup instance-implicit parameter via its parent projection:
Parameters to type classes may be marked with gadgets, which are special versions of the identity function that cause the elaborator to treat a value differently.
Gadgets never change the meaning of a term, but they may cause it to be treated differently in elaboration-time search procedures.
The gadgets outParam and semiOutParam affect instance synthesis, so they are documented in that section.
Whether a type is a class or not has no effect on definitional equality.
Two instances of the same class with the same parameters are not necessarily identical and may in fact be very different.
Instances are Not Unique
This implementation of binary heap insertion is buggy:
In the improved definitions, Heap'.bubbleUp is needlessly explicit; the instance does not need to be explicitly named here because Lean would select the indicated instances nonetheless, but it does bring the correctness invariant front and center for readers.
Most type classes follow the paradigm of a set of overloaded methods from which clients may choose freely.
This is naturally modeled by a product type, from which the overloaded methods are projections.
Some classes, however, are sum types: they require that the recipient of the synthesized instance first check which of the available instance constructors was provided.
To account for these classes, a class declaration may consist of an arbitrary inductive type, not just an extended form of structure declaration.
syntax
command::= ...
|`declModifiers` is the collection of modifiers on a declaration:
* a doc comment `/-- ... -/`
* a list of attributes `@[attr1, attr2]`
* a visibility specifier, `private` or `protected`
* `noncomputable`
* `unsafe`
* `partial` or `nonrec`
All modifiers are optional, and have to come in the listed order.
`nestedDeclModifiers` is the same as `declModifiers`, but attributes are printed
on the same line as the declaration. It is used for declarations nested inside other syntax,
such as inductive constructors, structure projections, and `let rec` / `where` definitions. declModifiersclassinductive`declId` matches `foo` or `foo.{u,v}`: an identifier possibly followed by a list of universe names declId`optDeclSig` matches the signature of a declaration with optional type: a list of binders and then possibly `: type` optDeclSigwhere(|`declModifiers` is the collection of modifiers on a declaration:
* a doc comment `/-- ... -/`
* a list of attributes `@[attr1, attr2]`
* a visibility specifier, `private` or `protected`
* `noncomputable`
* `unsafe`
* `partial` or `nonrec`
All modifiers are optional, and have to come in the listed order.
`nestedDeclModifiers` is the same as `declModifiers`, but attributes are printed
on the same line as the declaration. It is used for declarations nested inside other syntax,
such as inductive constructors, structure projections, and `let rec` / `where` definitions. declModifiersident`optDeclSig` matches the signature of a declaration with optional type: a list of binders and then possibly `: type` optDeclSig)*(derivingident,*)?
Class inductive types are just like other inductive types, except they may participate in instance synthesis.
The paradigmatic example of a class inductive is Decidable: synthesizing an instance in a context with free variables amounts to synthesizing the decision procedure, but if there are no free variables, then the truth of the proposition can be established by instance synthesis alone (as is done by the decide tactic).
In some cases, many related type classes may co-occur throughout a codebase.
Rather than writing all the names repeatedly, it would be possible to define a class that extends all the classes in question, contributing no new methods itself.
However, this new class has a disadvantage: its instances must be declared explicitly.
The Lean.Parser.Command.classAbbrev : commandExpands
```
class abbrev C <params> := D_1, ..., D_n
```
into
```
class C <params> extends D_1, ..., D_n
attribute [instance] C.mk
```
class abbrev command allows the creation of class abbreviations in which one name is short for a number of other class parameters.
Behind the scenes, a class abbreviation is represented by a class that extends all the others.
Its constructor is additionally declared to be an instance so the new class can be constructed by instance synthesis alone.
Because AddMul is a Lean.Parser.Command.classAbbrev : commandExpands
```
class abbrev C <params> := D_1, ..., D_n
```
into
```
class C <params> extends D_1, ..., D_n
attribute [instance] C.mk
```
class abbrev, no additional declarations are necessary to use plusTimes1 with Nat:
However, plusTimes2 fails, because there is no AddMul'Nat instance—no instances whatsoever have yet been declared:
#evalfailed to synthesize
AddMul' ?m.22
Additional diagnostic information may be available using the `set_option diagnostics true` command.plusTimes2257
failed to synthesize
AddMul' ?m.22
Additional diagnostic information may be available using the `set_option diagnostics true` command.
Declaring an very general instance takes care of the problem for Nat and every other type: