Closed avigad closed 7 years ago
I just added a small theory algebra/lattice/fixed_points
to library_dev
. This could provide a stopgap measure to define coinductive predicates. Again this would copy what Isabelle does.
It works as follows:
coinductive_predicate P : a -> Prop
| q_intro : forall a, q a -> P (t a)
| r_intro : forall a, r a -> P a -> P (s a)
would define a function P'
as
P' F a = (exists a', a = t a' /\ q a') \/ (exists a', a = s a' /\ r a' /\ F a')
The proof that P'
is monotone is straight forward and easy to automate. Then we define P = gfp P'
and with the lemma gfp_unfold
we derive P = P' P
and with this the required introduction rules. gfp_induct
provides us the induction rule.
For predicates we do not need a corecursor. And as we are in Prop
definitional equality is not a problem. Up to now the fixed point theory is constructive, I didn't try yet a concrete example but I guess the entire construction can be done constructively.
I have already a simple coinduction tactic (but nothing comparable to the equations compiler).
Adding coinductive datatypes to Lean is a major and complex project. It is unrealistic to expect an external contributor will be able to do it. Just to put things in perspective, the inductive compiler implements two transformations: it eliminates mutual and nested inductives. Its abstract description is much simpler than the one described here. Now, check the actual implementation: https://github.com/leanprover/lean/tree/master/src/library/inductive_compiler When @dselsam and I discussed it on the white board, everything looked simple, but @dselsam had to solve many unforeseen issues in the actual implementation.
Understood. Feel free to close the issue. My main goal was to make the notes publicly available in the issues, because I was asked to do so.
@avigad Github has a wiki feature. We can put this kind of note there.
I just added it to the wiki. Feel free to edit it. I will close the issue here.
The question often arises as to whether Lean will have coinductive types. Nobody involved in the project is planning to implement them in the foreseeable future, but there is agreement that it would be a nice feature to have. This issue includes some notes and suggestions that might be helpful to anyone interested in implementing them.
"Adding coinductive types to Lean" can mean one of two things: extending Lean's kernel with new constructions and operations or building coinductive types on top of existing primitives. Lean has adopted the design decision of keeping the kernel as small and simple as possible; for example, it implements a minimal form of inductive data type with primitive recursors (à la Dybjer). Other forms of inductive types and recursion, such as nested and mutual inductive types, arbitrary structural recursion, well-founded recursion, etc. have to be "compiled down" to those. We would therefore expect coinductive types to be defined in Lean, without changes to the kernel.
Isabelle has implemented a powerful coinductive datatype package along those lines: https://people.mpi-inf.mpg.de/~jblanche/co-data-impl.pdf. There are a few things that make the project more complicated in Lean:
The first issue is important because we would like to generate bytecode from such definitions or compile them to native code. Compilers can recognize coinductive definitions and handle them in special ways, but constructive definitions provide a computational semantics, thereby specifying intended behavior.
Ken Sakayori (@sakas--), an undergraduate at the University of Tokyo at the time, visited Carnegie Mellon for three weeks in the summer of 2015. Together we worked out a proposal for implementing coinductive types in Lean, and Ken carried out a proof-of-concept example by hand. Some notes I wrote up afterwards and Ken's formalization (both with snippets of Lean 2 code) are found below.
Implementing this in Lean 3 would require a lot of work. Someone would have to:
The first is not so hard. It may soon even be possible to write the parser extension in Lean itself. But the rest are difficult. Building expressions of dependent type theory automatically is fiddly and subtle, and there will likely be many unforseen corner cases. (It should be possible to carry out all or at least substantial parts of the constructions in Lean itself, with the metaprogramming facilities and API.) Users will expect the same gadgets that are being developed for inductive types (like mutual-nested-dependent-inductive-coinductive constructions and recursion). And all the work will be for nothing unless someone is able to keep the package going and respond to issues, questions, bug reports, and so on.
That said, I will now reproduce the notes on the construction that Ken and I proposed, and add some comments from Leo at the end. Ken has made his Lean 2 code available here: https://gist.github.com/sakas--/2758e8ebfc91db45526c. The notes below also use Lean 2 syntax.
To illustrate the idea, let us consider an inductive type with three constructors:
In other words, a
two_tree
is either a leaf, a labeled node with one child, or a labeled node with two children. We chose this example because it is just complicated enough to stand for a general case.The coinductive version, a
lazy two tree
would be similar, except that some branches might be infinite. The idea is to model this as a sequence of finite approximations. For every n, we have a depth n approximation, in which any branch that has not terminated yet is markedcontinue
.To do this, we only have to add an index to the definition above, and a constructor for
continue
:We want to say that a lazy two tree is a sequence of approximations, one for each
n
, such that then
th approximation agrees with the(n+1)
st. Here is what it means for consecutive approximations to agree:We can then define a lazy two tree to be a sequence of approximations, such that each agrees with the next:
That is really the core idea. The definition of
agrees
makes sure there is progress, i.e. if(agrees t1 t2)
holds, thent2
refines anycontinue
left int1
. We can then go on to define predicatesis_nil
is_cons
is_cons₂
and functionshead
tail
head₂
tail₂₁
tail₂₂
. Ken worked this all out in the gist. It is tedious, but it is entirely formulaic: you define the operations on approximations, you show that they preserve agreement, and then you list the definition to sequences.What about corecursion? The idea is that given
f : 1 + A * X + A * X * X
we should be able to define a function corec f satisfyingWe can write this in a nice way be defining
and then defining
Ken did all this, and got it to work. He proved, for example,
This is a definitional reduction rule in Coq; we only have it propositionally. The other reduction rules should be similar (though tedious) to prove.
I think this solution is very general. Here are two things I am not yet sure of:
For this example, Ken had to write almost 600 lines of Lean code (and it is not quite finished). But it is mostly boilerplate, and will hopefully be automatable. One nice thing is that it does not seem to require a lot of new syntax. Instead of
we want to write
and have the Lean generate all the stuff for us.
There is a small wrinkle, in that we have to generate constants
is_nil
is_cons
is_cons₂
head
tail
head₂
tail₂₁
tail₂₂
. The first three can be taken from the names of the constructors, but users need some way of specifying the others. Isabelle has you do it something like this:Lean 3 now supports such syntax for inductive types.
Another issue is to come up with nicer syntax for writing corecursive definitions. A full-blown effort will require extending the function definition system. But even a no-frills solution, writing
where ... is any expression of type
shape X A
is not too bad. It is intuitive:shape X A
just specifies which constructor you want to use, in our case,nil
,cons
, orcons₂
. It seems no more difficult than using therec
primitives for inductive types in Lean.After reading these notes, Leo offered the following suggestions.
It is really useful to devise schematic proofs (aka templates) that work for any coinductive declaration. To design this kind of template, it is useful to try many examples by hand. At least, this is the approach I used when I implemented features such as the
brec_on
definition.Here are some suggestions:
Reflexive coinductive types. We say an inductive type
T
is reflexive if it contains at least one constructor that takes as an argument a function returningT
. Example:It is also useful to try to simplify the proofs and get them close to a general template as possible. Here is an example where I tried to simplify some of the definitions/theorems in Ken’s file.
https://gist.github.com/leodemoura/fa65c20b50a391456d32