Zulip Chat Archive
Stream: lean4
Topic: New release of Functional Programming in Lean
David Thrane Christiansen (Jul 07 2022 at 21:14):
The second release of Functional Programming in Lean is up. This release completes Chapter 1. As always, feedback is more than welcome here or via email.
I hope you find it useful! It's available at https://leanprover.github.io/functional_programming_in_lean/ .
David Thrane Christiansen (Aug 07 2022 at 18:36):
Hello again! The third release is up. It contains Chapter 2, which describes IO
, lake
, and introduces do
-notation.
Mac (Aug 07 2022 at 23:13):
@David Thrane Christiansen One note: the feline
example could use lake new feline exe
instead to generate just an executable configuration.
František Silváši (Aug 07 2022 at 23:32):
I love the way you introduce IO actions, no monoids in the category of endofunctors, no algebraic effects; 10/10 :).
A few very minor observations:
a) 2.1 - Anatomy of a Greeting
Because this action doesn't read any interesting information from the environment in the process of emitting the string, IO.println has type String → IO Unit
I don't think it's immediately obvious as to what the type would be if it did indeed read anything interesting from the environment, but perhaps it's just my inability to process the sentence properly :).
b) 2.2 - IO Actions vs Values
It's structure....
Just a typo from 'Its structure'.
c) 2.4 - Main
This is more so a nitpick and realistically completely irrelevant in a Lean book, but in specifically C (and rules are different in C++):
0) you'll never see a standard-compliant main
that returns void unless you want to rely on /* or in some other implementation-defined manner. */
in the standard
1) technically, any function in C that does not specify void
in the parameter list takes any number of arguments; as such, especially main should be explicitly labelled as int main(void)
if you want the one that takes no arguments
Tom (Aug 08 2022 at 03:00):
The counter worker/cook analogy is really nice but it threw me off a bit upon first reading and I had to do a mental "flip" when I reached the conclusion.
Once I got to the end of the paragraph it was clear the counter worker is the IO monad that interacts with the real world but my expectation reading initially was exactly the opposite: I, the client, am interacting with the nice/clean/polite and friendly counter worker Lean (it also made sense in the sense of requesting what I want, i.e. being declarative) while, behind the scenes, the cook (the runtime) does all the "heavy lifting".
David Thrane Christiansen (Aug 08 2022 at 07:08):
Mac said:
David Thrane Christiansen One note: the
feline
example could uselake new feline exe
instead to generate just an executable configuration.
Thanks! I'll consider revising it. I think there's some value in people editing the file, just to get the experience, but it's also nice to show the available conveniences.
David Thrane Christiansen (Aug 08 2022 at 07:17):
@František Silváši I've created an issue on my tracker to make sure I follow up on these points - they'll be fixed in the next release. Thank you! And given that C has a strong culture of reading and adhering to standards, rather than just what implementations actually do, I think it's important to respect it so as not to lose C programmers who are less sold on Lean. This feedback is very valuable (as are the other points), so thank you very much!
David Thrane Christiansen (Aug 08 2022 at 07:18):
@Tom thanks for the feedback! I'll think a bit more about that analogy.
Patrick Massot (Aug 08 2022 at 09:50):
I like the analogy and I read it correctly from the beginning but of course I already know about monads. David, maybe you could simply insert a stronger hint at the very beginning clarifying who is the cook and who is the counter worker.
Paul Chisholm (Aug 14 2022 at 04:48):
A couple of comments on chapter 1:
- 1.6 Polymorphism-Option
Might be worth mentioning coercion allows the body ofList.head?
to be written
match xs with
| [] => none
| y :: _ => y
and Lean infers the y
really means some y
. This may be a little advanced for an introductory text, but it is the sort of thing that could catch out newcomers from languages where the some
is mandatory. Perhaps it would be better in the Additional Conveniences
section.
- 1.7 Additional Conveniences
Would be worth mentioning that match cases can be combined when the expression on the RHS is the same, so thehalve
example can be written:
def halve : Nat → Nat
| 0
| 1 => 0
| n + 2 => halve n + 1
Paul Chisholm (Aug 14 2022 at 09:37):
Another couple of things it would be good to cover:
- The difference between
=
and==
(even if just to note==
is used for the moment and=
will be explained inFrom Programming to Proving
). - Explanation of the
.name
notation where Lean infers the namespace from context (I see this in a couple of the examples and in Zulip topics, but not in the documentation yet that I have observed).
Henrik Böving (Aug 14 2022 at 09:43):
Note that we would usually write
def halve : Nat -> Nat
| 0 | 1 => 0
| n + 2 => halve n + 1
Not splitting the combined case out over multiple lines
David Thrane Christiansen (Aug 14 2022 at 12:25):
@Paul Chisholm thanks for the feedback!
Lean infers the
y
really meanssome y
I'm intentionally avoiding all coercions until the feature can be explained properly. Lean has so many features that their interactions can be quite overwhelming even for people with deep experience with proof assistants and functional programming, and this text is intended for readers with zero experience in either. I need to be careful in how much they get in chapter 1, but coercions are absolutely on the radar for later in the book, and this is a good example.
David Thrane Christiansen (Aug 14 2022 at 12:29):
I've created issues in my repository tracking each of the points that you brought up. I think that Ch1 is already longer than it should be, but it's certainly important to get to all of them at some point. Thanks again!
Paul Chisholm (Aug 14 2022 at 22:53):
@David Thrane Christiansen Thanks for the response. The book is shaping up nicely. I think it will be an invaluable resource for Lean programming.
@Henrik Böving That formatting makes sense. Is there, or is there planned, a Lean style guidelines, or even an automated code formatter?
Henrik Böving (Aug 14 2022 at 22:56):
The plan is to eventually get the delaborator to a point where it can act as a pretty printer for Lean. However right now it isn't even guaranteed to produce things that typecheck/evaluate to the same thing as their input properly so that is still a long way to go but we have people investigating and working on the issue right now.
David Thrane Christiansen (Sep 17 2022 at 17:25):
@František Silváši 🦉 Thanks again for the comment! I've fixed the issues you identified on my main branch, and the corrections will find their way into the imminent release.
David Thrane Christiansen (Sep 18 2022 at 18:33):
It's that time again! There's a fresh release of Functional Programming in Lean available for your reading pleasure. This time around, the first half of the type class chapter is available. It's later than usual this month due to my summer vacation and ICFP, but releases will be returning to the usual schedule next month.
As usual, feedback is welcome both here and in my email. Thank you for reading it!
Tom (Sep 19 2022 at 02:07):
Thank you!
Very minor nit: On the first page, you write:
an instance is created that contains an implementation of each operation for the new type. For instance, a type class named Add
I wonder if in this case the second instance :upside_down: of the world "instance" (emphasis mine) would be better served with "example" for clarity's sake. (This also re-occurs later in the chapter)
Paul Chisholm (Sep 19 2022 at 02:40):
Good description of type classes.
A few minor typos:
- 3.1, Overloaded Addition, second paragraph. "While an understanding the full..." -> "While an understanding of the full..."
- 3.1, Overloaded Multiplication, first paragraph. "...with the same time" -> "...with the same type"
- 3.1, Literal Numbers, second last paragraph. Missing '.' at end of the second sentence, immediately before 'Just'.
- 3.2, Methods and Implicit Arguments, first paragraph. "because the declaring a" -> "because declaring a"
- 3.2, Methods and Implicit Arguments, third paragraph. ""In order for the Lean" -> "In order for Lean"
- 3.3, Heterogenous Overloadings, second paragraph. "For instance, given the above two instances..." -> "Given the above two instances...". The repeated use of 'instance' seems awkward.
František Silváši 🦉 (Sep 20 2022 at 09:51):
This reads very coherently, I genuinely like this.
I have two notes that are of stylistic nature and can be safely disregarded :).
0) In 3.1. - open Plus (plus)
the 'selective export of plus' feature is first introduced (unless I am misremembering!) here, which is a section 'Classes and Instances'; it feels to me a little bit buried here if this is to be the first exposition to the feature. Maybe its first mention in 2.3. 'Libraries and Imports' might be a nicer place.
1) In 3.1. - 'Conversion to Strings', in the context of:
Additionally, if a type has a ToString instance, then it can be used to display the result of #eval...
This is, to me, begging for a short explanation of the difference between Repr
and ToString
, but I can also imagine this being elsewhere (or not present at all :)), as there are admittedly better contexts for this.
Again, this is very much a 'personal take', do feel more than encouraged to ignore this, there's nothing wrong with either of these :).
David Thrane Christiansen (Oct 29 2022 at 18:54):
@Paul Chisholm I've fixed all those in the latest version, thanks! And sorry it took me a bit to get back to you here.
David Thrane Christiansen (Oct 29 2022 at 18:54):
@František Silváši 🦉 These are very good suggestions that I've put into the book's issue tracker.
David Thrane Christiansen (Oct 29 2022 at 18:55):
Halloween is approaching, but there's no need to be afraid of proofs or of type classes.
A new release of Functional Programming in Lean is up, containing the rest of the chapter on type classes as well as updates based on your excellent feedback. This release also contains a short interlude that lightly introduces the idea of propositions, proofs, and truth, because they were really necessary in order to explain things like GetElem
.
As always, your feedback is much appreciated.
Patrick Massot (Oct 29 2022 at 21:39):
In https://leanprover.github.io/functional_programming_in_lean/type-classes/coercion.html, the last section seems outdated since CoeHead
and CoeTail
are discussed in this chapter.
Patrick Massot (Oct 29 2022 at 21:41):
A very minor point: in several places you use tree names as examples, but this will add completely unnecessary friction for people whose native language isn't English. I have no idea what is a birch or a beech. I understand that it doesn't matter, but it still slows me a tiny bit while reading.
Patrick Massot (Oct 29 2022 at 21:44):
In https://leanprover.github.io/functional_programming_in_lean/type-classes/coercion.html, there is an inconsistency betwenn howMany
and howMuch
.
Patrick Massot (Oct 29 2022 at 21:45):
In https://leanprover.github.io/functional_programming_in_lean/type-classes/pos.html#another-representation I don't understand the example, but maybe I forgot a previous explanation of this syntax.
Patrick Massot (Oct 29 2022 at 21:47):
Oh I see: this is the syntax to overload the constructor name, right? I always forget about this possibility.
František Silváši 🦉 (Oct 30 2022 at 01:28):
Another good chapter :).
Minor notes - once again, really just stylistic (in addition to what Patrick's already mentioned):
In 4.5 - Equality and Ordering, in the context of:
From the perspective of mathematics, two functions are equal if they map equal inputs to equal outputs, so this statement is even true, though it requires a two-line proof to convince Lean of this fact.
Mathematics doesn't technically 'need to' do extensional, it's just the common set-theoretic one; might be worth rephrasing.
In 4.6 - Chaining Coercions:
There's a mention of CoeHead that's like: oh yeah, this exists. I would actually be interested in seeing an example of where this is useful, because frankly, I can't think of one and a quick grep of the Lean 4 repo comes up with a single instance of this, some deep in Syntax that's far from immediately straightforward to understand.
In 4.6 - Design Considerations
'First off,coercion' - typo, space after comma
Very enjoyable read btw.
Arien Malec (Oct 30 2022 at 02:00):
Thanks for this!
In the coercions chapter should the need to tag coercion methods with @[coe]
be mentioned? I'm not clear on when that attribute is required but I do know that it can trip one up.
Patrick Massot (Oct 30 2022 at 09:59):
František Silváši 🦉 said:
In 4.5 - Equality and Ordering, in the context of:
From the perspective of mathematics, two functions are equal if they map equal inputs to equal outputs, so this statement is even true, though it requires a two-line proof to convince Lean of this fact.
Mathematics doesn't technically 'need to' do extensional, it's just the common set-theoretic one; might be worth rephrasing.
At the level of detail of this book about mathematics, I think it is really pointless to mention that some extremely specialized areas of mathematics need more refined versions of function equality. This has nothing to do with foundations of mathematics, it is simply the meaning of the word function in informal real world mathematics.
František Silváši 🦉 (Oct 30 2022 at 17:25):
I don't necessarily disagree; that doesn't mean the sentence can't be phrased in a slightly different way that also includes the fact that this is 'merely' the most common interpretation. Of course, this is exceedingly minor :).
David Thrane Christiansen (Nov 05 2022 at 10:54):
@Patrick Massot Thanks for all the great suggestions! I've fixed the errors in the development version of the book, and I added a little reminder about the structure constructor syntax in the exercise. WRT the tree names: I realy don't want to use lists of numbers, as my experience is that meaningful examples are much more meaningful for most people. But it sounds like picking trees from my local ecosystem is not a good solution for a big population of readers, so I'll think about some different themes to use (or at least drop some of the more similar-sounding ones).
David Thrane Christiansen (Nov 05 2022 at 10:59):
@František Silváši 🦉 : I'm aiming for a level of mathematics background here that is quite low. Just as I'm not planning on having an extensive discussion of constructive vs classical reasoning (except just enough to talk about computable vs noncomputable definitions), I'm not planning on talking about intensional vs extensional characterizations of functions or any such thing. I'll be mostly just appealing to the ideas of sets and functions that people learn when they're 13 years old, because that's not what this is about. I suspect that anyone with the background to stumble over this will also be in a position to realize that they're at a different level.
Thanks!
David Thrane Christiansen (Nov 05 2022 at 11:04):
Arien Malec said:
Thanks for this!
In the coercions chapter should the need to tag coercion methods with
@[coe]
be mentioned? I'm not clear on when that attribute is required but I do know that it can trip one up.
I'm actually not aware of that attribute! I can't find any instances of it in the Lean source code. Can you point me to some information so I can investigate? Thanks!
Arien Malec (Nov 05 2022 at 15:17):
Here’s the thread where I ran into the coercion registration issue:
Petar Maymounkov (Nov 05 2022 at 16:37):
I am following _Functional programming in lean_ and trying to define a basic type which is ordered.
I am puzzled as to why I get an error when I try to evaluate the < operator:
inductive Bit where
| zero : Bit
| one : Bit
open Bit
def Bit.compare (x y : Bit) : Ordering :=
match (x, y) with
| (zero, zero) => Ordering.eq
| (zero, one) => Ordering.lt
| (one, zero) => Ordering.gt
| (one, one) => Ordering.eq
instance : Ord Bit where
compare := Bit.compare
#eval zero < zero -- <- failed to synthesize
Notification Bot (Nov 05 2022 at 16:42):
This topic was moved here from #lean4 > instantiating Ord and "failed to synthesize instance" by Petar Maymounkov.
Sebastian Ullrich (Nov 05 2022 at 16:43):
Currently Ord
does not automatically imply LT
(which powers <
). This is likely to improve soon: https://github.com/leanprover/lean4/issues/1777
Sebastian Ullrich (Nov 05 2022 at 16:44):
You can use
instance : LT Bit := ltOfOrd
for now
Petar Maymounkov (Nov 05 2022 at 16:45):
Sebastian Ullrich said:
Currently
Ord
does not automatically implyLT
(which powers<
). This is likely to improve soon: https://github.com/leanprover/lean4/issues/1777
But I have the same issue with LT:
inductive Bit where
| zero : Bit
| one : Bit
open Bit
def Bit.lt (x y : Bit) : Prop :=
match (x, y) with
| (zero, zero) => false
| (zero, one) => true
| (one, zero) => false
| (one, one) => false
instance : LT Bit where
lt := lt
#eval zero < zero -- <-- fail
Sebastian Ullrich (Nov 05 2022 at 16:48):
Ah, you cannot #eval
propositions - they don't have a value. Lean is trying to be helpful and convert it to a Bool
using the Decidable
typeclass, but there is no instance of that for Bit
. If you use ltOfOrd
as above, you get a Decidable
instance for free.
Sebastian Ullrich (Nov 05 2022 at 16:49):
def Bit.lt (x y : Bit) : Bool := -- Bool!
match (x, y) with
| (zero, zero) => false
| (zero, one) => true
| (one, zero) => false
| (one, one) => false
instance : LT Bit where
lt a b := lt a b
also works fwiw
Mario Carneiro (Nov 05 2022 at 16:49):
Is that true? I think you still have to register it
Mario Carneiro (Nov 05 2022 at 16:51):
it is true
instance : Ord Bit where
compare := Bit.compare
instance : LT Bit := ltOfOrd
#eval zero < zero -- works
Sebastian Ullrich (Nov 05 2022 at 16:51):
Sebastian Ullrich (Nov 05 2022 at 16:52):
Since we're already using ltOfOrd
, it's fine to infer the rest
Mario Carneiro (Nov 05 2022 at 16:52):
I would have thought that declaring the instance would make that instance not apply anymore since it is wrapped
Mario Carneiro (Nov 05 2022 at 16:53):
that is, here:
instance myLt : LT Bit := ltOfOrd
we would end up with Decidable (@LT.lt Bit myLt)
instead of Decidable (@LT.lt Bit ltOfOrd)
Sebastian Ullrich (Nov 05 2022 at 16:56):
Typeclass inference unfolds reducibles and instances
David Thrane Christiansen (Nov 05 2022 at 18:03):
Arien Malec said:
Here’s the thread where I ran into the coercion registration issue:
As far as I can tell, this is a mathlib4 concern, rather than one of Lean per se. Mathlib4 is explicitly out of scope for this book. But thanks!
eyelash (Nov 05 2022 at 19:38):
I noticed something minor in chapter 4.4:
much like a Java ArrayList, a C++ std::vector, or a Rust std::vec.
The data structure in Rust is actually called Vec
(with an upper case V), std::vec
is just the module where that data structure is defined. std::vec::Vec
is also part of the prelude so it probably makes sense to refer to it as just Vec
.
eyelash (Nov 05 2022 at 19:58):
I also find the sentence "arrays occupy a contiguous region of memory, which is much better for processor caches" a bit confusing since AFAIK (someone please correct me if I'm wrong) the values of the array are still boxed in Lean, so only the pointers are stored in a contiguous region, especially after the comparison to C++ and Rust where the values themselves are actually stored in a contiguous region of memory.
James Gallicchio (Nov 05 2022 at 20:24):
Scalars will be stored unboxed. But, yeah, many objects are boxed P:
Arien Malec (Nov 05 2022 at 21:14):
@David Thrane Christiansen none of that code depended on Mathlib4
David Thrane Christiansen (Nov 05 2022 at 21:38):
eyelash said:
I noticed something minor in chapter 4.4:
much like a Java ArrayList, a C++ std::vector, or a Rust std::vec.
The data structure in Rust is actually called
Vec
(with an upper case V),std::vec
is just the module where that data structure is defined.std::vec::Vec
is also part of the prelude so it probably makes sense to refer to it as justVec
.
Thanks! I fixed this in the latest development release.
David Thrane Christiansen (Nov 05 2022 at 21:39):
eyelash said:
I also find the sentence "arrays occupy a contiguous region of memory, which is much better for processor caches" a bit confusing since AFAIK (someone please correct me if I'm wrong) the values of the array are still boxed in Lean, so only the pointers are stored in a contiguous region, especially after the comparison to C++ and Rust where the values themselves are actually stored in a contiguous region of memory.
This is a good point, so I've created an issue to track it.
David Thrane Christiansen (Nov 05 2022 at 21:40):
Arien Malec said:
David Thrane Christiansen none of that code depended on Mathlib4
As far as I can see, that attribute only exists in mathlib4. But I'll ask about it next time I talk to Lean developers on the phone - I could very well be overlooking something.
Sebastian Ullrich (Nov 05 2022 at 22:33):
It's in std4 now docs4#Tactic.NormCast.Attr.coe
Evgenia Karunus (Nov 06 2022 at 16:13):
In https://leanprover.github.io/functional_programming_in_lean/type-classes/coercion.html #eval (buildResponse "Functional Programming in Lean" Str "Programming is fun!").asString
the .asString
call doesn't work for me with the environment does not contain 'JSON.asString'
.
David Thrane Christiansen (Nov 26 2022 at 14:46):
eyelash said:
I also find the sentence "arrays occupy a contiguous region of memory, which is much better for processor caches" a bit confusing since AFAIK (someone please correct me if I'm wrong) the values of the array are still boxed in Lean, so only the pointers are stored in a contiguous region, especially after the comparison to C++ and Rust where the values themselves are actually stored in a contiguous region of memory.
I fixed this in the latest development version. It's now more clear that it's referring to the array itself being in a contiguous block, rather than having a pointer at each node like a linked list. Thanks again!
David Thrane Christiansen (Nov 26 2022 at 14:50):
Evgenia Karunus said:
In https://leanprover.github.io/functional_programming_in_lean/type-classes/coercion.html
#eval (buildResponse "Functional Programming in Lean" Str "Programming is fun!").asString
the.asString
call doesn't work for me withthe environment does not contain 'JSON.asString'
.
Thanks for this. The implementation of JSON.asString
is a bit hairy, so I was hoping to get away with not having it along. I'll beautify it and add it in the next release.
David Thrane Christiansen (Nov 30 2022 at 21:12):
The November release is out at https://leanprover.github.io/functional_programming_in_lean/ ! The new chapter covers monads, and I'm now hard at work on writing about monad transformers.
Thank you all for your ongoing support and feedback, the book is much improved by your comments.
Arien Malec (Nov 30 2022 at 21:54):
The way you present the motivation in terms of obvious patterns before using the "M" word is lovely.
David Thrane Christiansen (Nov 30 2022 at 21:58):
Thanks! The M-word is scary for people who haven't seen it before, and experience in classrooms has made it crystal clear to me that most people do better seeing examples before definitions, so that's how I tried to write it.
Kevin Buzzard (Nov 30 2022 at 22:13):
Did you take a look at "programming in Lean"? This is a half-finished lean 3 book from five years ago which taught me what a monad was (by going through four examples). I suspect Jeremy wrote it, he's good at writing stuff which teaches me things
Jeremy Avigad (Dec 01 2022 at 01:26):
Yup, that was me. Bas Spitters once told me that the reason that there are so many monad tutorials online is that everyone who finally understands them is so proud of themselves that they are compelled to write a tutorial. I visited Microsoft in the summer of 2016 and I learned how to use monads. I was so proud of myself that I wrote a tutorial.
David Thrane Christiansen (Dec 01 2022 at 08:51):
For some of the readers I want to reach, I think that the definition-first approach taken in his book will not work as well as the example-first approach that I'm going for here. I'm also intentionally trying to avoid mathematical jargon, though I didn't entirely succeed :-)
Moritz Doll (Dec 01 2022 at 09:00):
I've had a brief look and I think the example first approach is very good. Even though I am a mathematician by training I hate all of the 'monads are a part of category theory' nonsense, since it does not help you to understand anything and I was slightly sad that keeps appearing.
David Thrane Christiansen (Dec 01 2022 at 09:05):
Ideally, there are different learning resources for different audiences, adapted to their specific needs. I know people who learned Haskell after knowing a fair bit of category theory already, and they had quite different challenges in learning to program with monads than most people!
Programming with monads requires approximately zero category theory, just as you really don't need to know any graph theory to implement A* search for pathfinding in a game.
Patrick Massot (Dec 01 2022 at 10:48):
I think this chapter still lacks an introduction warning readers that you will first solve a problem in a very painful way for the sake of motivating some abstract gadget. Otherwise innocent readers will simply be terrified by the first examples.
Patrick Massot (Dec 01 2022 at 10:50):
And, as far as I remember, Jeremy's explanation doesn't feature any category theory.
Mario Carneiro (Dec 01 2022 at 10:57):
It might also help to give people a taste of what the final result is, since otherwise it's not really clear where it is all going. The endpoint of the Option
example is not really that satisfactory:
def firstThirdFifthSeventh (xs : List α) : Option (α × α × α × α) :=
xs[0]? ~~> fun first =>
xs[2]? ~~> fun third =>
xs[4]? ~~> fun fifth =>
xs[6]? ~~> fun seventh =>
some (first, third, fifth, seventh)
I sure am glad I don't have to write that and can do this instead:
def firstThirdFifthSeventh (xs : List α) : Option (α × α × α × α) := do
return (← xs[0]?, ← xs[2]?, ← xs[4]?, ← xs[6]?)
Arien Malec (Dec 01 2022 at 16:39):
Patrick Massot said:
I think this chapter still lacks an introduction warning readers that you will first solve a problem in a very painful way for the sake of motivating some abstract gadget. Otherwise innocent readers will simply be terrified by the first examples.
well the handrolled Monadic functions specialized to Option
, etc. is exactly how Rust gets on, with some special ?
syntax for Option
and Result
where short circuiting on failure is what's wanted. And and the piles of nested function calls is exactly how Promise based async code used to work in JS until everyone borrowed async/await
from C# which ultimately was from…Haskell (via Simon Marlow).
Jireh Loreaux (Dec 01 2022 at 19:47):
Mario, the only difference between what appears in the book and your example is some syntactic sugar right? In that case, I think an easy fix without breaking too much the flow would be to add this right before the section Logging:
"In Section 5.3 we'll learn some that we have some syntactic sugar which can simplify this even further to:
def firstThirdFifthSeventh (xs : List α) : Option (α × α × α × α) := do
return (← xs[0]?, ← xs[2]?, ← xs[4]?, ← xs[6]?)
But for now we'll postpone explanation of this syntax in order to consider several more examples."
This succinct version should then also be included in Section 5.3. (@David Thrane Christiansen of course, these are only suggestions!)
David Thrane Christiansen (Dec 01 2022 at 19:49):
These are great suggestions, thanks everyone!
Sebastian Ullrich (Dec 02 2022 at 16:39):
I saw a certain trend on Mastodon today, I wonder if it's helpful here as well...
ChatGPT on "write biblical verses in the style of the king james bible in the form of a rap explaining the concept of monads from functional programming"
František Silváši 🦉 (Dec 03 2022 at 18:01):
Another chapter written with a very good 'flow' to it :). Maybe introducing the way of creating infix notations in a chapter 'Monad' is not ideal, but I understand it's very much on 'needed now' bases. This book is excellent.
David Thrane Christiansen (Jan 08 2023 at 21:00):
Hi everyone! There's a new release out at https://leanprover.github.io/functional_programming_in_lean/, with a chapter on Applicative
that takes a few necessary detours through universe polymorphism and structure inheritance. I haven't yet made the improvements suggested up-thread, but they're certainly on the radar still. Thanks again for reading!
David Thrane Christiansen (Jan 08 2023 at 21:01):
Next up is monad transformers. It's already half written, but I realized that I needed to already have Applicative
to make progress so I backtracked a bit :-) It was a busy December.
Marcus Rossel (Jan 08 2023 at 22:12):
In Universes > Prop
and Polymorphism:
Prop
is at the bottom of the universe hierarchy, and the type ofProp
isType
. This means that lists of propositions have typeList Type
:
def someTruePropositions : List Prop := [
1 + 1 = 2,
"Hello, " ++ "world!" = "Hello, world!"
]
... should the "have type List Type
" actually be "have type List Prop
"?
Henrik Böving (Jan 08 2023 at 22:31):
Yes it should be i think.
Yaël Dillies (Jan 08 2023 at 22:33):
The fact that Prop : Type
is irrelevant here, I think
David Thrane Christiansen (Jan 09 2023 at 13:24):
Marcus Rossel said:
In Universes >
Prop
and Polymorphism:
Prop
is at the bottom of the universe hierarchy, and the type ofProp
isType
. This means that lists of propositions have typeList Type
:def someTruePropositions : List Prop := [ 1 + 1 = 2, "Hello, " ++ "world!" = "Hello, world!" ]
... should the "have type
List Type
" actually be "have typeList Prop
"?
Nice catch!
David Thrane Christiansen (Jan 10 2023 at 07:42):
All right, it's fixed in the development release of the book. Thanks again!
Yaël Dillies (Jan 12 2023 at 10:12):
In the section on types, it looks like this sentence is missing a word: To use a type that can represent the negative integers, provide a it directly:
Yaël Dillies (Jan 12 2023 at 10:16):
In functions and definitions, I think you meant
so the preceding function types can be written `Nat -> Nat` and `Nat -> Nat -> Nat`, respectively.
rather than
so the preceding function types can be written `Nat -> Bool` and `Nat -> Nat -> Nat`, respectively.
Yaël Dillies (Jan 12 2023 at 10:51):
In Polymorphism, Just as C# and Java require type arguments to provided explicitly
should be Just as C# and Java require type arguments to be provided explicitly
Arien Malec (Jan 12 2023 at 20:59):
I think it would be helpful to note that while you are working through inheritance of structures, it generally better to use typeclasses rather than structural inheritance to encapsulate common behavior.
David Thrane Christiansen (Jan 14 2023 at 09:19):
Yaël Dillies said:
In the section on types, it looks like this sentence is missing a word:
To use a type that can represent the negative integers, provide a it directly:
Thanks! It was actually just a spurious "a", which is now deleted.
David Thrane Christiansen (Jan 14 2023 at 09:26):
Yaël Dillies said:
In functions and definitions, I think you meant
so the preceding function types can be written `Nat -> Nat` and `Nat -> Nat -> Nat`, respectively.
rather than
so the preceding function types can be written `Nat -> Bool` and `Nat -> Nat -> Nat`, respectively.
Thanks! I've fixed it. This was one of the few types shown in the book that wasn't machine-checked - I've rectified that now :-)
David Thrane Christiansen (Jan 14 2023 at 09:26):
Yaël Dillies said:
In Polymorphism,
Just as C# and Java require type arguments to provided explicitly
should beJust as C# and Java require type arguments to be provided explicitly
This is now fixed as well. Thank you for your sharp eyes and careful attention!
David Thrane Christiansen (Jan 14 2023 at 09:27):
Arien Malec said:
I think it would be helpful to note that while you are working through inheritance of structures, it generally better to use typeclasses rather than structural inheritance to encapsulate common behavior.
This is a good note to have somewhere, but I don't think it should be there because inheritance of type classes has not yet been introduced. I'll think about the right place to put it, along with a way of explaining why.
František Silváši 🦉 (Jan 14 2023 at 12:23):
You know a chapter is great when 'the best' people can find is minor typos! :)
- 6.2: Checking whether an [sic] value of type α is in the subtype...
- 6.7: Defining a class defines a structure, and additionally creates a [sic] empty table of instances...
David Thrane Christiansen (Jan 14 2023 at 14:18):
František Silváši 🦉 said:
You know a chapter is great when 'the best' people can find is minor typos! :)
- 6.2: Checking whether an [sic] value of type α is in the subtype...
- 6.7: Defining a class defines a structure, and additionally creates a [sic] empty table of instances...
:embarrassed: I should have caught those proofreading! Sorry, and thanks for the kind way of pointing them out :-)
Yaël Dillies (Jan 15 2023 at 19:41):
- 2.6 : Missing
s
inWhile most language allow
-
- : The sentence
Computer-aided theorem provers like Lean are designed to allow mathematicians to write proofs while omitting many details, while the software fills in the missing explicit steps, decreasing the likelihood of oversights or mistakes.
reads a bit heavy. Maybe because of the duplicated "while"?
- : The sentence
-
propositions are a kind of type
. I know "type" is referring top : Prop
, but it could be misread as meaningProp
because of the singular
Jireh Loreaux (Jan 16 2023 at 03:59):
David Thrane Christiansen said:
This is a good note to have somewhere, but I don't think it should be there because inheritance of type classes has not yet been introduced. I'll think about the right place to put it, along with a way of explaining why.
How about a footnote, possibly with a forward reference?
David Thrane Christiansen (Jan 16 2023 at 08:53):
Yaël Dillies said:
- 2.6 : Missing
s
inWhile most language allow
- : The sentence
Computer-aided theorem provers like Lean are designed to allow mathematicians to write proofs while omitting many details, while the software fills in the missing explicit steps, decreasing the likelihood of oversights or mistakes.
reads a bit heavy. Maybe because of the duplicated "while"?
propositions are a kind of type
. I know "type" is referring top : Prop
, but it could be misread as meaningProp
because of the singular
Thanks, these are fixed in the development branch!
David Thrane Christiansen (Jan 16 2023 at 08:56):
Jireh Loreaux said:
How about a footnote, possibly with a forward reference?
I don't think footnotes work well in writing that's intended to be consumed online - they end up being a bit like endnotes in paper books, where they're so inconvenient to consult that they might as well not be there. And Markdown/mdbook unfortunately doesn't have reasonable support for asides, marginalia, or other things that work well for this task.
I'll puzzle over it.
Jireh Loreaux (Jan 16 2023 at 16:18):
Footnotes with return hyperlinks (generally denoted by a ↩) I find to be easily read even online, but it's fine if you disagree. Dealing with non-linearity of topics is always hard.
Ian Young (Jan 22 2023 at 22:45):
I haven't really got this far yet*, but while I was reading ahead, I noticed that in 6.2 Applicative Functors: Validated Input the code block following "Finally, these three components can be combined using seq:" is the same as the previous code block, whereas the following text implies that it should define checkInput
.
*I'm finding myself a bit confounded by the Even Number Literals exercise in 4.2. But that's another story.
David Thrane Christiansen (Jan 26 2023 at 13:53):
Thanks for catching the mistake! It's now fixed in the development branch and will be out in the next release. The code should read:
def checkInput (year : Nat) (input : RawInput) : Validate (Field × String) (CheckedInput year) :=
pure CheckedInput.mk <*>
checkName input.name <*>
(checkYearIsNat input.birthYear).andThen fun birthYearAsNat =>
checkBirthYear year birthYearAsNat
David Thrane Christiansen (Jan 26 2023 at 13:56):
Here's a hint for even number literals: it's using recursive instance search, so you're going to want one instance that is the base case for the search, and another instance that takes a literal for some smaller Even
(via finding an OfNat
for that smaller Even
) and transforms it into a literal for a somewhat larger Even
.
Start with your base case: it should give a literal for the smallest Even
number. Get that working first.
Ian Young (Jan 26 2023 at 19:44):
@David Thrane Christiansen Thanks for the hint. That was what I'd thought I tried, but it fails to synthesize an instance for values larger than 0:
inductive Even : Type where
| zero : Even
| succ2 : Even → Even
def Even.toNat : Even → Nat
| Even.zero => 0
| Even.succ2 n => n.toNat + 2
instance : ToString Even where
toString x := toString (x.toNat)
instance : OfNat Even 0 where
ofNat := Even.zero
instance [OfNat Even n] : OfNat Even (n + 2) where
ofNat := Even.succ2 (OfNat.ofNat n)
#eval (0: Even)
#eval (2: Even)
What's particularly confusing me is that the very similar definition in this thread clearly does work, and adapting that I can get this:
inductive GEven : Nat → Type where
| zero : (m : Nat) → GEven m
| succ2 : {m : Nat} → GEven m → GEven m
instance : OfNat (GEven m) m where
ofNat := GEven.zero m
instance [OfNat (GEven m) n] : OfNat (GEven m) (n + 2) where
ofNat := GEven.succ2 (OfNat.ofNat n)
abbrev Even : Type := GEven 0
#check (0 : Even)
#check (2 : Even)
Which works, but seems a bit mad! Clearly my first attempt isn't allowing it to 'chain together' the base and recursive cases, but I don't see why not.
David Thrane Christiansen (Jan 26 2023 at 20:10):
Try replacing instance : OfNat Even 0 where
with instance : OfNat Even Nat.zero where
. That works for me, at least - I should figure out and describe this gotcha!
David Thrane Christiansen (Jan 26 2023 at 20:13):
Your latter example has some aspects that don't make sense to me - the succ2
constructor doesn't add two anywhere, for instance, and the zero
constructor seems to say that every Nat
is even. Then your first instance of OfNat
should work for any Nat
literal at all, even odd ones. The second instance doesn't relate m
to n+2
in any way.
Ian Young (Jan 26 2023 at 20:28):
Yes, using Nat.zero
instead of 0
there works for me. Thanks!
My second attempt made sense to me (except for its over-complication): GEven m
represents even numbers from m
upwards (in the same way as GNat n
in the referenced thread represents natural numbers from n
upwards).
Ian Young (Jan 26 2023 at 21:55):
What I don't think I was expecting is that with either of these definitions, #check (254 : Even)
works, but #check (256 : Even)
just says "failed to synthesize instance OfNat Even 256". Given the point at which it fails, I presume the latter case is 'running out of gas'?
David Thrane Christiansen (Jan 27 2023 at 07:33):
That seems like the right explanation - another TODO for the book! Thanks!
David Thrane Christiansen (Jan 27 2023 at 08:21):
The thing about your GEven
example is that it seems to me that it also represents odd numbers when m
is odd. In particular,
example : GEven 1 := .zero 1
If I wanted to write an inductive family to represent only the even numbers greater than or equal to some given number, I'd want to do something like:
inductive GEven (basis : Nat) : Nat → Type where
| base : basis % 2 = 0 → GEven basis basis
| plusTwo : GEven basis n → GEven basis (n + 2)
The issues with your version are:
- It doesn't ensure that the base number is even
- It doesn't track the actual Nat
The second issue could be solved pretty well by having a function that converted it to a Nat. But the first one really does need the k % 2 = 0
bit on the base case.
Here's a proof that the version here represents even numbers greater than or equal to some base number:
theorem geven_is_even (n : Nat) (even : GEven basis n) : n % 2 = 0 := by
induction even <;> simp [*]
case plusTwo _ ih =>
have step (n : Nat) : (n + 2) % 2 = n % 2 := by
have : (n + 2) % 2 = if 0 < 2 ∧ 2 ≤ n + 2 then (n + 2 - 2) % 2 else n + 2 := Nat.mod_eq (n + 2) 2
have : 2 ≤ n + 2 := by simp_arith
simp [*, Nat.add_sub_self_right n 2]
simp [*]
theorem geven_is_ge (n : Nat) (even : GEven basis n) : n ≥ basis := by
simp_arith
induction even <;> simp
case plusTwo _ ih =>
constructor; constructor; assumption
The other version that doesn't track the specific even Nat
at the type level works basically the same way:
inductive GEven (basis : Nat) : Type where
| base : basis % 2 = 0 → GEven basis
| plusTwo : GEven basis → GEven basis
def GEven.toNat : GEven k → Nat
| .base _ => k
| .plusTwo more => toNat more + 2
theorem geven_is_even (even : GEven basis) : even.toNat % 2 = 0 := by
induction even <;> simp [*]
case plusTwo _ ih =>
simp [GEven.toNat]
have step (n : Nat) : (n + 2) % 2 = n % 2 := by
have : (n + 2) % 2 = if 0 < 2 ∧ 2 ≤ n + 2 then (n + 2 - 2) % 2 else n + 2 := Nat.mod_eq (n + 2) 2
have : 2 ≤ n + 2 := by simp_arith
simp [*, Nat.add_sub_self_right n 2]
simp [*]
theorem geven_is_ge (even : GEven basis) : even.toNat ≥ basis := by
simp_arith
induction even <;> simp [GEven.toNat]
case plusTwo _ ih =>
constructor; constructor; assumption
They're so similar because the constraints that the indexed family imposes on the index are precisely the computation behavior of GEven.toNat
in this case.
Ian Young (Jan 28 2023 at 14:05):
Sorry, I seem to have wasted your time here with an offhand remark. The point of my GEven
was just to get an answer to the exercise after my original attempt didn't work.
David Thrane Christiansen (Jan 29 2023 at 07:36):
Don't worry - it was fun :-)
Ian Young (Jan 29 2023 at 17:15):
Moving on to 4.3. Controlling Instance Search: Default Instances:
instance [Add α] : HPlus α α α where hPlus := Add.add
With this instance,
hPlus
can be used for any addable type, likeNat
:#eval HPlus.hPlus (3 : Pos) (5 : Nat)
Shouldn't this #eval
have two Nat
arguments?
Ian Young (Jan 29 2023 at 19:32):
In 4.4. Arrays and Indexing: Non-Empty Lists
For example, the non-empty list
idahoSpiders
(which contains some spider species native to the US state of Idaho) consists of"Banded Garden Spider"
followed by four other spiders, for a total of five spiders.
Were you intending this to be followed by such a definition? From the later examples (in 4.5. Standard Classes: Appending), it's clearly just:
def idahoSpiders : NonEmptyList String :=
{ head := "Banded Garden Spider",
tail := ["Long-legged Sac Spider", "Wolf Spider", "Hobo Spider", "Cat-faced Spider"] }
But it breaks the flow if I have to go looking for spiders to put in the list... and then I'll get distracted, wondering why you didn't include the bold jumping spider Phidippus audax, because, let's face it, jumping spiders are the best!
David Thrane Christiansen (Jan 31 2023 at 10:22):
I'll check those out, thanks!
David Thrane Christiansen (Jan 31 2023 at 10:22):
The January release is now out, featuring monad transformers and fancy do
-notation features!
Floris van Doorn (Jan 31 2023 at 10:47):
7.1 typo: "Files whose names being" -> "Files whose names begin"
The section "What counts as a do block?" is very clarifying. I was hesitant using return
in nested do-blocks, since I was unsure what block was returning this value.
Sebastian Ullrich (Jan 31 2023 at 11:21):
Note that your editor should also highlight the corresponding do
keyword when you put the cursor on a return
statement
Patrick Massot (Jan 31 2023 at 17:50):
Congratulations @David Thrane Christiansen! The monad transformer chapter is a very nice milestone. It definitely feels like we now have a full book about the core concepts of programming using Lean. Actually I'm not sure what the next chapter will be about.
Patrick Massot (Jan 31 2023 at 17:51):
Is Chapter 9 meant to be about proving properties of programs written in Lean or theorem proving?
Patrick Massot (Jan 31 2023 at 17:53):
Do you intend to stop after Chapter 9 or do you want to include a Chapter 10 about interfacing with other programming languages (the famous FFI stuff that seems pretty tricky to setup)? What about giving more advanced explanations about lake?
David Thrane Christiansen (Jan 31 2023 at 18:56):
Ian Young said:
Shouldn't this
#eval
have twoNat
arguments?
Fixed on the development branch! Thanks!
David Thrane Christiansen (Jan 31 2023 at 19:01):
Ian Young said:
Were you intending this to be followed by such a definition?
I was, and it's now there. Thank you again!
Ian Young said:
why you didn't include the bold jumping spider Phidippus audax, because, let's face it, jumping spiders are the best!
As far as I can see, they're not native to Idaho. I tried to include nature examples from ecosystems that have been important parts of my life, like Idaho, Cascadia, and southern Scandinavia. They look like cool spiders, though!
David Thrane Christiansen (Jan 31 2023 at 19:20):
@Patrick Massot The next chapter is intended to be an introduction to programming with indexed families, with things like dependent pattern matching, a repeat of the difference between definitional and propositional equality and why the difference exists, and some of the fun things that dependent types allow in programs. This is a thing that people will not likely have seen elsewhere, and while Lean culture doesn't use it nearly as much as Agda or Idris culture, understanding things like inaccessible patterns is still useful and important.
I was originally going to have 9 be about proofs with tactics _per se_, but I've been outlining and exploring an alternate structure. The alternate structure starts with a discussion of the cost model of Lean programs, describing things like the "immutable beans" optimizations and tail recursion. This naturally leads one to want to write some programs that use arrays mutably (e.g. insertion sort, which is in-place and stable). Many array algorithms are not structurally recursive, which provides a natural hook for talking about termination proofs. These termination proofs are good motivation for proving certain basic things about Nat
, and it puts them in a context that's directly useful for programming.
The book will stop after that, at least for now, as the funding that MSR has so generously provided will be running out. I'll be getting Saturdays with my family back :-) Perhaps we'll revisit the collaboration in the future if the book turns out to bring lots of value - I've had fun working on it, at least, though I do need a break. I'll be spending April open-sourcing the infrastructure for CI and examples, proofreading it a couple of times, looking for ways to improve the integration between the sections, checking the exercises for difficulty and distribution, ensuring that there are enough exercises, and so forth.
The reason I sometimes include the wrong example in the book is that I don't write the text as a literate program, because that makes it too hard to get the flexibility I need for the kinds of explanations I want to do. Instead, there's a bunch of Lean macros. So for idahoSpiders
, my source file contains:
book declaration {{{ idahoSpiders }}}
def idahoSpiders : NonEmptyList String := {
head := "Banded Garden Spider",
tail := [
"Long-legged Sac Spider",
"Wolf Spider",
"Hobo Spider",
"Cat-faced Spider"
]
}
stop book declaration
Then the book text (now) contains:
For example, the non-empty list `idahoSpiders` (which contains some spider species native to the US state of Idaho) consists of `{{#example_out Examples/Classes.lean firstSpider}}` followed by four other spiders, for a total of five spiders:
```lean
{{#example_decl Examples/Classes.lean idahoSpiders}}
```
The build script contains some horrid regexp munging that replaces the directives in the text with the associated bits from the text. This allows me to make sure that the text and the examples are always in sync. For things like error messages and #eval
output, I have macros that reify the message as a string in the source file and fail if there's a mismatch, like this:
expect error {{{ northernTreesEight }}}
northernTrees[8]
message
"failed to prove index is valid, possible solutions:
- Use `have`-expressions to prove the index is valid
- Use `a[i]!` notation instead, runtime check is perfomed, and 'Panic' error message is produced if index is not valid
- Use `a[i]?` notation instead, result is an `Option` type
- Use `a[i]'h` notation instead, where `h` is a proof that index is valid
⊢ 8 < Array.size northernTrees"
end expect
More regexp magic extracts this to the rendered book. This means that I simultaneously keep error messages in sync with the Lean compiler, and keep the text in sync with the tests.
I think that this use of the metaprogramming system could be useful for others, so I'll definitely open and document it towards the end. I even have a macro for equational reasoning style informal proofs. Here's an example:
namespace MonadApplicativeProof1
axiom m : Type → Type
@[instance] axiom instM : Monad m
@[instance] axiom instLM : LawfulMonad m
axiom α : Type
axiom v : m α
equational steps {{{ mSeqRespId }}}
pure id >>= fun g => v >>= fun y => pure (g y)
={
by simp [LawfulMonad.pure_bind]
-- `pure` is a left identity of `>>=`
}=
v >>= fun y => pure (id y)
={
-- Reduce the call to `id`
}=
v >>= fun y => pure y
={
-- `fun x => f x` is the same as `f`
}=
v >>= pure
={
-- `pure` is a right identity of `>>=`
by simp [LawfulMonad.bind_pure_comp]
}=
v
stop equational steps
end MonadApplicativeProof1
The comments contain Markdown that's extracted to the book, and the terms in the middle are checked in a way similar to calc
(no term defaults to rfl
). This makes it all quite easy to maintain and prevents my scattered brain from skipping important steps.
David Thrane Christiansen (Jan 31 2023 at 19:23):
That whole equational steps
thing is actually quite simple to implement too:
declare_syntax_cat eqSteps
syntax term : eqSteps
syntax term "={" "}=" eqSteps : eqSteps
syntax term "={" term "}=" eqSteps : eqSteps
syntax withPosition("equational" "steps" "{{{" ws ident ws "}}}" (colGt eqSteps) "stop" "equational" "steps") : command
inductive Steps where
| done : Lean.Syntax → Steps
| cons : Lean.Syntax → Option Lean.Syntax → Steps → Steps
def Steps.process [Monad m] (forStep : Lean.Syntax × Option Lean.Syntax × Lean.Syntax → m Unit) : Steps → m Unit
| .done _ => pure ()
| .cons e1 why (.done e2) => forStep (e1, why, e2)
| .cons e1 why (.cons e2 why2 more) => do
forStep (e1, why, e2)
process forStep (.cons e2 why2 more)
partial def getSteps : Lean.Syntax → Lean.Elab.Command.CommandElabM Steps
| `(eqSteps|$t:term) => pure (.done t)
| `(eqSteps|$t:term ={ }= $more:eqSteps) => do
return (.cons t none (← getSteps more))
| `(eqSteps|$t:term ={ $why:term }= $more:eqSteps) => do
return (.cons t (some why) (← getSteps more))
| other => throwError "Invalid equational steps {other}"
elab_rules : command
| `(equational steps {{{ $name }}} $stepStx:eqSteps stop equational steps) =>
open Lean.Elab.Command in
open Lean.Elab.Term in
open Lean in
open Lean.Meta in do
let exprs ← getSteps stepStx
if let .done e := exprs then liftTermElabM <| withDeclName name.raw.getId do
let _ ← elabTerm e none
exprs.process fun
| (e1, why, e2) =>
liftTermElabM <| withDeclName name.raw.getId do
let x ← elabTerm e1 none
let y ← elabTerm e2 none
synthesizeSyntheticMVarsNoPostponing
match why with
| none =>
unless (← isDefEq x y) do
throwError "Example equational step {y} ===> {x} is incorrect\n----------\n\t {(← whnf y)}\n ≠\n\t {(← whnf x)}\n----------\n\t {(← reduceAll y)}\n ≠\n\t {(← reduceAll x)}"
| some p =>
let e1' : TSyntax `term := ⟨e1⟩
let e2' : TSyntax `term := ⟨e2⟩
let eq ← elabTermEnsuringType (← `($e1' = $e2')) (some (mkSort Level.zero))
let _ ← elabTermEnsuringType p (some eq)
synthesizeSyntheticMVarsNoPostponing
Patrick Massot (Jan 31 2023 at 20:14):
Thanks for the detailed explanations! I think it doesn't make much sense to have a chapter on proofs per se. There is already Theorem proving in Lean for that (and we'll port Mathematics in Lean at some point). It would be much more consistent to cover proofs for programming such as termination proofs. I guess that proofs of programs would need a lot more space, maybe a different book.
Patrick Massot (Jan 31 2023 at 20:16):
Stuff about lake and ffi would be a nice continuation but I understand that funding is running out, and the current plan is already a full self-contained book.
Mario Carneiro (Jan 31 2023 at 20:16):
I think proofs need to be covered at some level, if only to inform readers that there's a whole other book to read if that's your interest
Mario Carneiro (Jan 31 2023 at 20:17):
ideally there should be some light proof examples for the kinds of proofs that come up in programming applications, like termination proofs and using the a[i]
array indexing operator
Mario Carneiro (Jan 31 2023 at 20:18):
I think teaching it as two separate languages sends the wrong message
Patrick Massot (Jan 31 2023 at 20:20):
Yes, David wrote he's thinking about covering termination proofs.
Arien Malec (Jan 31 2023 at 20:32):
Another lens might be proving things that would be unit tests in other languages, as well as designing an API surface area that's amenable to proof.
Arien Malec (Jan 31 2023 at 20:35):
The canonical killer app for dependently typed languages is the "typestate pattern" - e.g., a auth component that provably can't be in the wrong state
James Gallicchio (Jan 31 2023 at 20:39):
Arien Malec said:
Another lens might be proving things that would be unit tests in other languages, as well as designing an API surface area that's amenable to proof.
my impression was both these things are still major research areas, no? :grimacing:
Mario Carneiro (Jan 31 2023 at 20:43):
examples with Connection (open : Bool) : Type
and such are cute but honestly not that relevant to programming in lean and don't really sell the language when something like haskell is the point of comparison
Mario Carneiro (Jan 31 2023 at 20:44):
more interesting stuff would be a type which only holds prime numbers, or an acyclic graph structure
Arien Malec (Jan 31 2023 at 20:46):
I come from a Rust perspective & this article was highly influential...
https://docs.idris-lang.org/en/latest/st/machines.html
Mario Carneiro (Jan 31 2023 at 20:46):
You can't even really do the Connection
thing very well because lean doesn't have linear types
Mario Carneiro (Jan 31 2023 at 20:46):
which is what you actually need to make that example work
Arien Malec (Jan 31 2023 at 20:46):
E.g., http://cliffle.com/blog/rust-typestate/
Mario Carneiro (Jan 31 2023 at 20:47):
and it doesn't play well with monads either
Arien Malec (Jan 31 2023 at 20:47):
huh...
Mario Carneiro (Jan 31 2023 at 20:49):
that idris example can't be done in lean directly because the do notation doesn't have typestate like that
Arien Malec (Jan 31 2023 at 20:49):
then yeah, a dag example would be useful...
Paul Chisholm (Feb 01 2023 at 07:56):
@David Thrane Christiansen
Assorted minor comments on the first 4 chapters. Many are stylistic in nature so just ignore if you don't agree.
1.3 Defining Functions
- 3rd para: remove second instance of
just
. - 6th para: Remove
s
fromAs a special cases
. - last para (before exercise): drop
actually
in 1st line.
1.4 Defining Type/Messages You May Meet
- 1st para: it says
...by a feature of Lean that has not yet been introduced
; at this point the reader has no clue what that feature is or why it matters. Perhaps a forward reference would clarify. - Despite the title of the section, it doesn't seem to be primarily about the messages.
- It should be an
h2
heading so it isn't insideDefining Types
. All otherMessages...
subsections are at theh2
level.
1.4 Structures
- Para starting
The typical way
:a instance
should bean instance
. - Same para: delete the first occurrence of
both
.
1.4 Behind the Scenes
- Para starting
Constructors have...
: delete first occurrence ofthat
.
1.5 Datatypes and Patterns
- In the third para you say
most user defined datatypes are inductive datatypes
. If you mean that in the general sense, so that structure definitions are included, then I agree. If you specifically mean types that are recursively defined, then I would argue most are structure definitions. - Para starting
This definition has...
: the 5th sentence seems to be a bit overloaded withname
- could it be reworded?
1.6 Polymorphism
- Para starting
In this example...
: two occurrences ofbecause
reads a little clumsy. - Last para: would
pattern-matches to run the same pattern
be better expressed aspattern-matches against the same pattern
? - Last para: a couple of
#check/eval
lines after the definition ofposOrNegThree
would be useful to clarify what is happening.
1.6 Implicit Arguments
- Para starting
In the standard library...
: delete the firstthat
.
1.6 Option
- Note in the definition of
Option
the decoration: Type
has been added just before thewhere
, and this appears consistently in all subsequent definitions. All prior type definitions omit it. - Where you have
#eval [].head? (α := Int)
, might note that#eval ([]:List Int).head?
is also possible?
1.6 Prod
- Where you note
using domain terminology can make it easier to read the code
, might also add specific structures help to avoid errors.
1.7 Local Definitions
- Consider giving an alternate definition of
helper
usingwhere
(which interestingly doesn't requirerec
).
1.7 Anonymous Functions
- Consider using
\mapsto
instead of=>
, though that is compromised since\mapsto
can't be used in patterns. - The centred dot is
\.
or\centerdot
, not\cdot
.
2.3 Lakefiles
- Since the standard library
std4
has now been separated from core, and is likely to be required for many packages, it would be good to show howstd4
is included
require std from git "https://github.com/leanprover/std4" @ "main"
2.4 Getting Started
- For consistency with 2.3, it would be preferable to include the guillemets.
2.6 do Notation
- Should
and given a unique name
begives them a unique name
?
3 Indexing Without Evidence
- 1st para:
an question
->a question
. - Is the example
#eval woodlandCritters[1]!
supposed to show a crash? Is the index supposed to be out of bounds?
3 Exercises
- exercise 1,
rfl
not sufficient for5 < 18
.
4.1 Classes and Instances
- Someone following along and entering the examples may be a bit confused when they enter
#eval Plus.plus 5 3
and get an error rather than8
as shown. Not till later is it explained.
4.1 Conversion to Strings
- In definition of
posToString
possibly haves!"({s})"
for consistency. You perhaps avoided this as the twos
s might confuse?
4.1 Exercises
- To this point the exercises have been small, then we get HTTP, which is much bigger and requires background information. Subsequent exercises go back to being small again. It is certainly an interesting exercise, but perhaps a bit of a leap.
4.4 Arrays
- Para starting
In pure functional...
:contain an optimizations
->contain an optimization
orcontain optimizations
.
4.4 Non-Empty Lists
- Para starting
This fact can be...
: delete second instance ofthat
. - In definition of
NonEmptyList.get
, replaceok
with_
.
4.5 Equality and Ordering
- Having defined
Pos
as an instance ofOrd
, could add something like
instance : LT Pos where
lt x y := compare x y == Ordering.lt
#eval (1 : Pos) < (2 : Pos)
Possibly even
instance : LE Pos where
le x y := compare x y != Ordering.gt
#eval (1 : Pos) ≤ (2 : Pos)
and explain why the #eval
throws an error, though
#eval (1 : Pos) < (2 : Pos) || (1 : Pos) == (2 : Pos)
succeeds.
4.5 Deriving Standard Classes
- In one of the
Messages
sections might be worth mention thatderiving
sometimes throws messages like this:
default handlers have not been implemented yet, class: 'ToString' types: [X]
which suggests deriving may be possible in future for that class.
4.5 Functors
- Possibly note
(· + 5) <$> [1, 2, 3]
can also be written[1, 2, 3].map (· + 5)
.
4.6 Chaining Coercions
- Follow the definition of
coercedToB
with#eval coercedToB
to demonstrate it isB.b
(though that means you would needderiving Repr
onB
). - Para starting
Some coercions...
: containsin in
.
4.6 Coercing to Functions
- 1st para:
there are more than one way
->there is more than one way
.
4.6 Aside: JSON as a String
- Para starting
The function that emits...
: delete secondthat
inis complicated enough that Lean cannot see that the recursive calls...
.
4.7 Examples
- The sentence
Examples may create define functions by accepting arguments:
; did you intend both create and define here?
David Thrane Christiansen (Feb 01 2023 at 08:50):
@Paul Chisholm Thank you for your careful attention and helpful comments! I won't have time to work on the book for a bit due to FOSDEM, but I'll be sure to look over everything when I'm able.
Arthur Paulino (Feb 04 2023 at 12:58):
Hi! I have a suggestion for a topic: threads. Doing IO tasks in threads, sharing/locking data, joining etc.
I am a complete noob in the subject and it would be lovely to learn about those in Lean 4. What do you guys think?
David Thrane Christiansen (Feb 05 2023 at 06:36):
That would indeed be a good topic! However, the scope of this book is fixed, and there's no more time for additional topics. I'm really trying to get the parts through that are conceptually different from imperative programming, not comprehensively document the language and tools. Perhaps a thread chapter would be better suited to the manual?
Wojciech Nawrocki (Feb 05 2023 at 19:19):
Mario Carneiro said:
You can't even really do the
Connection
thing very well because lean doesn't have linear types
The linked ST
example is from Idris 1 which doesn't have linear types either
Wojciech Nawrocki (Feb 05 2023 at 19:25):
Arien Malec said:
then yeah, a dag example would be useful...
FWIW I have just started working on this. The code is super specific to the project it's for, but if it ends up working well it could be extracted out into its own library or std4 or somewhere.
Arien Malec (Feb 06 2023 at 02:14):
Super cool example!
Arien Malec (Feb 06 2023 at 02:22):
James Gallicchio said:
Arien Malec said:
Another lens might be proving things that would be unit tests in other languages, as well as designing an API surface area that's amenable to proof.
my impression was both these things are still major research areas, no? :grimacing:
My impression is that proving all things that would be unit tests in other languages feels hard but proving some things that would be unit tests n other languages is a superpower of a language like Lean, where the proof language and the programming language are the same thing. E.g., LawfulFoo
, or more practically something that would absolutely be part of the test suite: docs4#List.replicate_zero docs4#List.eq_replicate
David Thrane Christiansen (Mar 12 2023 at 21:07):
Hello everyone! There's a new release of the book out, with a chapter on programming with dependent types.
Thank you very much for the excellent feedback and careful reading so far. There's one more chapter to go!
https://leanprover.github.io/functional_programming_in_lean/
Malcolm Langfield (Mar 25 2023 at 20:46):
Not sure if this is still the place to submit errata, but there is a typo in the last paragraph of the introduction to Chapter 8:
Expressive specifications can be complicated to fulfill, and there is a real risk of creating tying oneself in knots and being unable to complete the program.
Probably "creating" should not be there?
David Thrane Christiansen (Apr 22 2023 at 19:28):
Malcolm Langfield said:
Not sure if this is still the place to submit errata, but there is a typo in the last paragraph of the introduction to Chapter 8:
Expressive specifications can be complicated to fulfill, and there is a real risk of creating tying oneself in knots and being unable to complete the program.
Probably "creating" should not be there?
This is still a good place - I just don't know how to get Zulip to notify me about posts to threads, and I missed this until now. It's fixed!
David Thrane Christiansen (Apr 22 2023 at 19:32):
@Paul Chisholm Thank you again for those thorough comments - they're all integrated in the latest development version of the book. I'm sorry it took me a while to get to them.
Last updated: Dec 20 2023 at 11:08 UTC