Skip to content

Explanation: Induction, pattern matching, fixed points #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

patrick-nicodemus
Copy link
Contributor

@patrick-nicodemus patrick-nicodemus commented Apr 3, 2025

I have written a document which I think best falls under the "Explanation" heading of https://diataxis.fr/

It explains induction and pattern matching, with some worked examples of dependent elimination. It needs a bit of polishing but overall comments would be good.

I'm not really familiar with mutual recursion, but I think I understand dependent elimination for a single inductive type pretty well and can write on that. I think the presentation here is good:
http://adam.chlipala.net/cpdt/html/MoreDep.html
and it would be good to incorporate some of that into the documentation.

@patrick-nicodemus patrick-nicodemus changed the title first draft Explanation: Induction, pattern matching, fixed points Apr 3, 2025
@thomas-lamiaux
Copy link
Collaborator

I have to look at it in more details but I think it is a very good first step for sth that is often confusing for beginners.
But, before moving forward, I think we should discuss what we want for inductive types because

  • I was thinking of having a more complete tutorial about inductive types that would also mention tactics to reason about them like induction, destruct etc... Explanation: Induction, pattern matching, fixed points #86. But doing so may create sth too long?
  • We should also discuss the target. Should we have one tutorial, or one for CS people and another version for maths people? This is a tutorial for CS people, and I think that have two is indeed the best.

@patrick-nicodemus
Copy link
Contributor Author

patrick-nicodemus commented Apr 3, 2025

I think that the tactics should be in a separate tutorial if usage is the primary goal.

I think thematically it makes sense to have one "explanation" be purely about Gallina with no tactic scripts, because it is a pedagogical piece about the theory behind CIC inductive definitions and how to use them in Rocq. The tactics can go in a second tutorial that refers to the explainer for the low level theory, and maybe suggests common mistakes (your induction hypothesis is too weak and you need to revert more variables)

On the other hand if we are talking about the relationship between induction and destruct tactics and the Gallina terms they can go here, helping to bridge the mental gap between proof scripts and gallina

@thomas-lamiaux
Copy link
Collaborator

I'll read it more carefully, and tell you more about it next week.
It would be good to ask on zulip what people think about that.

use these as our basic examples.
*)

Notation "x -> y" := (forall _ : x, y) (at level 99, right associativity, y at level 200).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this file meant to run with -noinit? if not what is this Notation for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason the prelude wasn't loading when I edited this, I probably just didn't have my environment configured properly. It's fine to load the prelude for this file.

@thomas-lamiaux
Copy link
Collaborator

@patrick-nicodemus I have looked at it in details. First, here is some general feedback. I'll write a review just after to make technical comments on the code and the explanations.

Right now, the explanations are a bit weird because it is kinda written as an explanation, even though the content is more the one of a tutorial. This is explaining how to write function by fixpoint and pattern-matching in practice, talking about stuff like in .. as .. return, introducing concept and difficulties step by step. But, this content really is the one of a tutorial. An explanation would be more like "How does strict positivity work, and why it is needed" or "How does the guard condition work?" giving an overview of how a feature works internally, for instance, to explain why it works like that in practice.

I think this should really be a tutorial "Introduction to Inductive Types, and Writing Functions on them by Fixpoint and Pattern-Matching". It is almost already there, and it is really needed. It can then be completed by a second tutorial, "how to reason about inductive types".

Copy link
Collaborator

@thomas-lamiaux thomas-lamiaux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It mostly need reordering

Comment on lines +16 to +25
In this section we explain the basics of inductive types and pattern
matching in Rocq. We focus on the fragment of the language which
Rocq shares with languages based on the Hindley-Milner type system
with algebraic data types, such as OCaml and Haskell,
deferring dependent types to the next section.

Programmers who are experienced in OCaml or Haskell
can skim this section quickly to learn the Rocq syntax
for pattern matching and recursive function definitions via the
[fix] combinator.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not really a good argument because everything is always dependent in Rocq, including parameters.
I think it is best to introduce the different inductive types as mentioned in #81, in order sth like:

  1. Basic ones like nat
  2. Mutual inductive types
  3. parametrized inductive types
  4. index inductive types, and non-uniform parameters
  5. nested inductive types

Comment on lines +120 to +126
(** The examples we have considered so far are "enumerated types",
where the constructors for the inductive type [T] have been constants
inhabiting T. In the following example, we show that the programmer
can declare constructors which are functions from another type into [T].
We also illustrate that inductive types, like other kinds of definitions,
can be *parameterized* by arguments, which defines a *family* of inductive
data types. *)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Start a new section about parametrized inductive types. Move and discuss recursion in the previous section.

Comment on lines +247 to +251
Fixpoint add (n m : nat) :=
match n with
| O => m
| S n0 => S (add n0 m)
end.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice, everyone uses Fixpoint. So in a tutorial, you want to explain

  1. there is a fix constructor, how it works, what is the point
  2. explain how it relates to Fixpoint
  3. stick to Fixpoint for the rest of the tutorial unless fix really is needed, in which case you need to explain why

Comment on lines +230 to +232
The programmer rarely has to write [{struct m}] explicitly, as
Rocq can usually infer it, but it is likely to appear in Rocq's
output when they are printing proof terms that use induction.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rocq will just try them all. This can make the code longer to type check, and the "wrong one" can be picked, making unfolding weird though it is rare.

Comment on lines +306 to +308
Inductive EvenOdd : bool -> Type :=
| ZeroEven : EvenOdd true
| S_EO : forall (b : bool), EvenOdd b -> EvenOdd (negb b).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This really is not the usual definition of Even and Odd. This not a mutual inductive type. This is an encoding of mutual inductive type using indices. Mutual inductive type are primitive in the kernel and that it is how they should be used unless people know what they are doing.

This will be confusing to newcomers, and they are much better examples of index inductive types, like the predicate All. This part is to be removed entirely.

Comment on lines +389 to +396
The type of natural numbers [nat] that we defined earlier
can be defined in language with algebraic data types
like OCaml or Haskell. However, even though the type itself
may seem fairly simple, the pattern matching rule for
for defining functions on the natural numbers must account for
dependent typing: the elimination rule does not just tell us how to
define a function [nat -> A], but how to define an element of
[forall n: nat, P] where [P] may depend nontrivially on [n].
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything is always dependent in Rocq. This is weird to introduce more complicated inductive type to go back to nat. Most explanations about match should be moved earlier, showng that Rocq can infer it in simple cases.

Comment on lines +568 to +573
(** ** 4. Case study: Equality

The propositional equality type is a relatively
simple example of an indexed inductive type which allows us to
work through some examples without too much additional complexity.
*)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be part of the section index inductive type, provided it really adds sth

Comment on lines +369 to +382
(** In the example above, if you remove [: vector A (n' + m)], it
won't compile. Rocq is unable to infer the return type unless it is
explicitly annotated. As a general rule, if your code doesn't
compile, adding more type annotations and filling in implicit
arguments may help you get better error messages, and make the
typechecker happy, _up to a point_.

However, if you don't understand how to correctly communicate type
annotations to the compiler, then all your efforts will be in
vain, which is why it's so important to understand how to read and
write dependent pattern matching type annotations in Rocq.

Thus, we turn to the details of dependent pattern matching.
*)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type checking is decidable only if types are all given. If they are not given Rocq tries to infer what it can, but there is no guaranty.

Comment on lines +555 to +566
(** Try the same trick here of looking at the return type using [refine]
to see how the context and return type changes inside the branches
[| tt => _ ] and [| ff => _ ]. *)

Definition bool_induction :
forall P : bool -> Type,
P true -> P false -> forall b : bool, P b :=
fun P pf_tt pf_ff b =>
match b as b' return P b' with
| true => (* 1 *) pf_tt
| false => (* 2 *) pf_ff
end.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part alternates between:

  • the weird def of is_even
  • vectors
  • nat
  • vectors
  • is_even
  • bool

That makes stuff non linear and confusing.

But adding more annotations can help you get better error messages.
Once you get it to compile, you can try deleting the type
annotations and see if it still compiles.
*)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a section about nested inductive type like RoseTree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants