Zulip Chat Archive
Stream: general
Topic: First maintenance release of Functional Programming in Lean
David Thrane Christiansen (Oct 03 2023 at 08:50):
I'm happy to announce the first maintenance release of Functional Programming in Lean, an introductory book on using Lean as a programming language, which is intended to serve both programmers wanting to get into theorem proving and mathematicians wondering what this monad transformer thing is and why it's helpful for proof automation. In this release, a number of mistakes have been fixed and the text has been brought up to date with the changes in Lean version 4.1.0.
It's here: https://lean-lang.org/functional_programming_in_lean/
Ioannis Konstantoulas (Oct 03 2023 at 09:46):
Thank you very much for this update; without FPIL, I would have never gotten around to using Lean.
Filippo A. E. Nuccio (Oct 31 2023 at 09:12):
I am posting here a question about Section 2.2, but I wonder whether this is the right place (@David Thrane Christiansen : should FPIL have a dedicated stream?). I start by saying that I am a complete newby insofar "programming" is concerned, and I find the book deeply inspiring, incredibly well written and very clear: I am really sure my misunderstanding comes from my utter ignorance.
I am trying to understand the code
let stdin ← IO.getStdin
let stdout ← IO.getStdout
stdout.putStrLn "How would you like to be addressed?"
let input ← stdin.getLine
let name := input.dropRightWhile Char.isWhitespace
stdout.putStrLn s!"Hello, {name}!"
and I am puzzled about the usefulness of the first two lines. AFAIU, at the point where let stdin ← IO.getStdin
is executed, no input has been provided (and similarly, when let stdout ← IO.getStdout
is executed, no output exists yet). So, I do not understand what stdin
and stdout
really are after this execution. I suspect that (part of) my misunderstanding comes from a confusion about this IO
business. In particular, although I have appreciated the "cook vs. counter worker" metaphor in 2.1, I have the impression that no definition of what an "effect" or "side effect" was given. So the sentence
The cook's notes represent IO actions that are produced by evaluating Lean expressions, and the counter worker's replies are the values that are passed back from effects.
is very dense for me: I really do not know neither what IO actions nor what effects are.
Mario Carneiro (Oct 31 2023 at 09:19):
So, I do not understand what stdin and stdout really are after this execution.
They are "handles", to the input and output streams respectively. This controls whether your putStrLn
goes to the console output, as compared to the output of a child process, a file, or some other thing
Mario Carneiro (Oct 31 2023 at 09:21):
in this context an "effect" is something like a change in the state of the real world, e.g. the words How would you like to be addressed?
appearing on your console
Mario Carneiro (Oct 31 2023 at 09:23):
IO actions are the elements of type IO A
, they represent instructions to do a thing in the real world (an effect) and then report a result back (of type A
)
Mario Carneiro (Oct 31 2023 at 09:27):
for example IO.getStdout
is an IO action which (describes the act of) going and retrieving a handle to the current console output stream, and stdout.putStrLn "How would you like to be addressed?"
is an IO action which (describes the act of) printing How would you like to be addressed?
to the stream identified by the stdout
handle
Filippo A. E. Nuccio (Oct 31 2023 at 09:28):
Ah ok. So the actions that print the lines "Hi there!" into a .tex file or into a .txt file would both be represented by some IO term (probably of different, but similar type)?
Mario Carneiro (Oct 31 2023 at 09:31):
they would probably have the same type, namely IO Unit
Mario Carneiro (Oct 31 2023 at 09:32):
which is the type of all IO actions that do a thing in the real world and return no result
Mario Carneiro (Oct 31 2023 at 09:32):
they would have different bodies though (they would be different terms of this type)
Filippo A. E. Nuccio (Oct 31 2023 at 09:32):
Ah, so " writing something in the console/in a text file" is not a result?
Mario Carneiro (Oct 31 2023 at 09:33):
no, the result is the pure value produced as a result of the action, this is usually bound to a variable with the let x <- action
syntax
Mario Carneiro (Oct 31 2023 at 09:34):
so IO.getStdout
is an action with a result (the output stream which we bind to variable stdout
), while stdout.putStrLn "..."
has no result (or rather, it has a result of type Unit
that we don't bother to bind to a variable)
Mario Carneiro (Oct 31 2023 at 09:34):
it has an effect on the real world, but it doesn't pass any pure values back to the code
Filippo A. E. Nuccio (Oct 31 2023 at 09:35):
Mario Carneiro said:
it has an effect on the real world, but it doesn't pass any pure values back to the code
Oh, I guess that the "back" in this sentence is crucial.
Filippo A. E. Nuccio (Oct 31 2023 at 09:35):
Do you have an example of an action of type IO Nat
, if that makes any sense at all?
Mario Carneiro (Oct 31 2023 at 09:37):
def foo : IO Nat := IO.getNumHeartbeats
this gets some global state (the current state of the heartbeat counter)
Mario Carneiro (Oct 31 2023 at 09:37):
Anything with some kind of nondeterminism or real-world-state dependence is going to be an IO A
Mario Carneiro (Oct 31 2023 at 09:38):
because lean functions have to be pure functions, you can't have a Unit -> Nat
function which returns a different value every time you run it
Mario Carneiro (Oct 31 2023 at 09:39):
but foo
is effectively that, if you do
#eval do
let x ← foo
let y ← foo
println! "{x} =?= {y}"
then there is no guarantee that x
and y
end up with the same value
Filippo A. E. Nuccio (Oct 31 2023 at 09:41):
Oh, I see. So
def foo : fun n => n+1
has type Nat -> Nat
while
def bar : fun n => n + IO.getWeekNumber
(supposing that IO.getWeekNumber
exists, for the sake of this example) is of type Nat -> IO Nat
?
Filippo A. E. Nuccio (Oct 31 2023 at 09:43):
Mario Carneiro said:
because lean functions have to be pure functions, you can't have a
Unit -> Nat
function which returns a different value every time you run it
Can you speculate a bit about your use of "pure" here? What kind of gadget would you qualify as "non-pure function"?
Mario Carneiro (Oct 31 2023 at 09:43):
You also have to use bind in order to "sequence" IO actions, unlike pure functions. n + IO.getWeekNumber
does not typecheck because IO.getWeekNumber
has type IO Nat
, not Nat
. The correct way to write a function of type Nat -> IO Nat
there is
def bar : Nat -> IO Nat := fun n => do
let w <- IO.getWeekNumber
return n + w
where do
is sugar for a usage of the bind
function, which is used to compose IO
actions together
Mario Carneiro (Oct 31 2023 at 09:44):
A "pure function" is one whose type is not a monad thing like A -> IO B
, it's just A -> B
Filippo A. E. Nuccio (Oct 31 2023 at 09:45):
Mario Carneiro said:
You also have to use bind in order to "sequence" IO actions, unlike pure functions.
n + IO.getWeekNumber
does not typecheck becauseIO.getWeekNumber
has typeIO Nat
, notNat
. The correct way to write a function of typeNat -> IO Nat
there isdef bar : Nat -> IO Nat := fun n => do let w <- IO.getWeekNumber return n + w
where
do
is sugar for a usage of thebind
function, which is used to composeIO
actions together
Wow, this is enlightening. Thanks!
Filippo A. E. Nuccio (Oct 31 2023 at 09:46):
Mario Carneiro said:
A "pure function" is one whose type is not a monad thing like
A -> IO B
, it's justA -> B
But then the term you constructed above of type Nat -> IO Nat
is what? Not a function?
Mario Carneiro (Oct 31 2023 at 09:46):
You can think of A -> IO B
as a pure function, but it is a pure function for producing an "IO action" of type IO B
, it doesn't actually do the thing that this action describes. So for the same input it's always the same IO action output, but the resulting IO action may have different results when actually performing its effect using #eval
or similar
Filippo A. E. Nuccio (Oct 31 2023 at 09:46):
Ah! Got it!
Mario Carneiro (Oct 31 2023 at 09:46):
an IO Nat
is not a number, it is instructions for how you might get a number if you ask the universe to help
Filippo A. E. Nuccio (Oct 31 2023 at 09:48):
Yes, I see now. You are saying that from the lean-theoretic point of view it is a honest function assigning to every input the same output, but this output is the action, not the result of the action. And the same action can yield different results when performed at different moments, for instance. Right?
Filippo A. E. Nuccio (Oct 31 2023 at 09:49):
Thanks! I can dive back in some exercice and try not to drown... :sea_lion:
Filippo A. E. Nuccio (Oct 31 2023 at 09:50):
(As an aside: is IO short for Input-Output, or completely unrelated?)
Mario Carneiro (Oct 31 2023 at 09:52):
it is indeed input/output
Kevin Buzzard (Oct 31 2023 at 13:48):
Filippo A. E. Nuccio said:
Oh, I see. So
def foo : fun n => n+1
has type
Nat -> Nat
whiledef bar : fun n => n + IO.getWeekNumber
(supposing that
IO.getWeekNumber
exists, for the sake of this example) is of typeNat -> IO Nat
?
Filippo -- did you know that WithBot
is a monad? You can think of WithBot Nat
as "naturals + ", but you can also think of them as "a Nat, or perhaps a failure" and then you can just add these things up, but failure + x = failure. But there's a coercion from Nat to WithBot Nat and in the context of monads I think this is function is called pure
. WithBot
(which is really Option
) is a great beginner example of a monad. It also shows that you can't get out of the monad: if you have a term of type WithBot Nat
you can't just say "OK but what is the actual Nat?" because the answer might be "oh we failed in the calculation 10 lines up, so there is no Nat any more"
Filippo A. E. Nuccio (Oct 31 2023 at 14:49):
Oh, never though at WithBot
as a monad! Thanks for the insight.
Agnishom Chattopadhyay (Oct 31 2023 at 16:59):
Out of curiosity, how ready is Lean for general purpose programming? (i.e, writing executable programs and not just proving theorems)
Matthew Ballard (Oct 31 2023 at 17:07):
Multiple people have done Advent of Code with it, for example.
Matthew Ballard (Oct 31 2023 at 17:08):
I would say very ready
Jireh Loreaux (Oct 31 2023 at 17:11):
Indeed, very ready, as it is now being used even by AWS for the Cedar model.
Agnishom Chattopadhyay (Oct 31 2023 at 17:12):
That's really cool. I am very happy to see a general purpose programming language and a very competent theorem proving environment in one ecosystem. I googled and found this and this
Agnishom Chattopadhyay (Oct 31 2023 at 17:17):
Jireh Loreaux said:
Indeed, very ready, as it is now being used even by AWS for the Cedar model.
It seems that they are proving theorems about Cedar, rather than implement Cedar itself in Lean. GitHub Link
Daniil Kisel (Oct 31 2023 at 17:18):
There is an article describing building of a simple web API for a competition.
Jireh Loreaux (Oct 31 2023 at 17:27):
Right, I said it was used for the Cedar model. But of course that model includes the full specification of Cedar. See the cedar-spec/cedar-lean/Cedar/Spec
directory.
Patrick Massot (Oct 31 2023 at 17:32):
Agnishom Chattopadhyay said:
Out of curiosity, how ready is Lean for general purpose programming? (i.e, writing executable programs and not just proving theorems)
Lean as a programming language is already really nice, but don't expect that libraries exists. You can currently use it if you have no dependency at all (or use it to create a basic library). For instance you won't have regular expressions or http requests. So you need to think carefully before starting a project.
Adam Topaz (Oct 31 2023 at 20:03):
Right. The main thing missing is libraries. Also, it's a bit dangerous to program in Lean4... once you do it for a while, you realize how much better it is than any other programming language.
Filippo A. E. Nuccio (Nov 01 2023 at 11:42):
Mario Carneiro said:
IO actions are the elements of type
IO A
, they represent instructions to do a thing in the real world (an effect) and then report a result back (of typeA
)
@Mario Carneiro Retrieving this discussion, I have been thinking about this and I still don't understand well when reading FPIL: the definition there is
IO α
is the type of a program that, when executed, will either throw an exception or return a value of typeα
and I do not understand the role of the "exception" when comparing with your definition. For instance, IO Unit
should either throw an exception or a term of type Unit
. Now, looking at
def main : IO Unit := IO.println "Hello, world!"
how should I interpret the role of the "exception"?
Mario Carneiro (Nov 01 2023 at 11:45):
IO A
is actually defined as BaseIO (Except IO.Error A)
, where BaseIO
is what I described and Except
is another monad (it's equivalent to Sum
but with constructors named .ok
and .error
instead of the more generic .inl
and .inr
) which expresses the possibility of failure. So an IO action can perform an effect and then return a failure of type IO.Error
instead of an actual value of type A
Filippo A. E. Nuccio (Nov 01 2023 at 11:48):
And in the above case with main
, when would this failure be returned?
Mario Carneiro (Nov 01 2023 at 11:48):
It would be produced by main
when you execute it
Mario Carneiro (Nov 01 2023 at 11:49):
println
might throw an error if the underlying stream is closed, for example
Mario Carneiro (Nov 01 2023 at 11:49):
or if you are piping the output to a file and you run out of disk space
Mario Carneiro (Nov 01 2023 at 11:50):
the lean runtime, which is the one responsible for running main
, checks if this IO action returned an error and if so it prints some information about it to the console and exits with an error code
Mario Carneiro (Nov 01 2023 at 11:51):
or if you run main by just using #eval main
, the lean server is the one that catches this error and draws a red squiggle over the command and shows the error message in the infoview
Filippo A. E. Nuccio (Nov 01 2023 at 11:52):
Oh I see.
Filippo A. E. Nuccio (Nov 01 2023 at 11:54):
On the other hand, your example
def foo : IO Nat := IO.getNumHeartbeats
certainly produces a Nat
, but does it also have a side effect?
Mario Carneiro (Nov 01 2023 at 11:54):
It actually does, since if you run it twice you get different numbers
Mario Carneiro (Nov 01 2023 at 11:55):
it has the effect of increasing the number of heartbeats by 1 :)
Filippo A. E. Nuccio (Nov 01 2023 at 11:55):
Well, but this is the same for everything I do, even if it is not a IO
action, no?
Mario Carneiro (Nov 01 2023 at 11:55):
But in general IO actions aren't required to have an effect. In particular pure x
is an IO action that has no effect and returns x
Mario Carneiro (Nov 01 2023 at 11:58):
Filippo A. E. Nuccio said:
Well, but this is the same for everything I do, even if it is not a
IO
action, no?
This is a bit complicated. For things that aren't IO actions, any effects they have in reality (like allocations or consuming CPU cycles) are not observable from the lean type system
Mario Carneiro (Nov 01 2023 at 11:58):
this is actually more a statement about what lean can (or cannot) express than it is about what effects programs have
Filippo A. E. Nuccio (Nov 01 2023 at 11:59):
Oh, I see, very clear. Thanks again, I'll go back to the book!
Mario Carneiro (Nov 01 2023 at 11:59):
for example pointer equality is not observable as far as lean is concerned, because if you could observe it you can prove false
Filippo A. E. Nuccio (Nov 01 2023 at 12:00):
Wow, now you made me curious... why?
Mario Carneiro (Nov 01 2023 at 12:01):
Because you can create the same object twice in different locations, and lean will say that these objects are equal but the "address of" operation applied to them results in different values, which violates the congruence property of equality: applying a function to equal things yields equal results
Filippo A. E. Nuccio (Nov 01 2023 at 12:03):
Nice!
Mario Carneiro (Nov 01 2023 at 12:03):
Nevertheless, observing addresses and pointer equality is sometimes useful or necessary, so lean has these functions anyway... but they are marked as unsafe
: docs#ptrAddrUnsafe
Filippo A. E. Nuccio (Nov 01 2023 at 12:07):
I see, but we are steadily climbing beyond my paygrade now! :sweat_smile:
Siddhartha Gadgil (Nov 01 2023 at 12:14):
A simple case one gets IO Nat
is in generating a random number. In my course I first grumbled about this and then realized that it was for a very good reason, since we want:
- different invocations to give different values.
- A function applied to equal arguments give equal results.
One can "run" to a Nat but then it is clear that we have a state argument which is different for different invocations.
Personally (as a mathematician) I found this example useful for understanding as there is no need for a real world :smile: to justify needing state. Indeed, in my course I made a simple example separating only the relevant part of IO: https://github.com/siddhartha-gadgil/proofs-and-programs-2023/blob/2d3312143811f0679175c2614e454ad6d163608f/PnP2023/Lec_03_24/Sampling.lean#L100
Filippo A. E. Nuccio (Nov 01 2023 at 12:43):
And this example is the rand
or the run
function? Do you happen to have recorded your course?
Siddhartha Gadgil (Nov 01 2023 at 14:14):
Filippo A. E. Nuccio said:
And this example is the
rand
or therun
function? Do you happen to have recorded your course?
This example is to show state being used. The rand
function is like Lean's built in one, in that it gives a monadic value. Here it is RandomM Nat
instead of IO Nat
of Lean. The run
is one that can get us out of the Monad, or in general to one level below (TermElabM
to MetaM
to CoreM
...). Because the Monad here is a simple one the result of running is the pure value, without having to fall back to error.
I haven't recorded my course, though I did use Docgen4 to create docs: https://math.iisc.ac.in/~gadgil/proofs-and-programs-2023/doc/PnP2023.html with many details.
Filippo A. E. Nuccio (Nov 01 2023 at 14:16):
Thanks!
David Thrane Christiansen (Nov 05 2023 at 19:12):
I'm catching up a bit on Zulip after a week of heads-down concentrated coding - thank you all for taking such good care of @Filippo A. E. Nuccio 's questions!
@Filippo A. E. Nuccio - thank you for reading the book carefully, and for showing up here with questions. It makes me very happy that you're getting value out of it, and that you're putting as much work into reading it as went into writing it. You've basically skipped ahead to chapter 5 in this discussion session :-)
Thanks again!
Filippo A. E. Nuccio (Nov 05 2023 at 19:30):
Thank you for writing it (and to all the people who helped me, of course)! I am now trying to attack your cat/feline/(will there be tigers? lions? :tiger: ) example, with some struggle and much fun. You'll certainly see more questions.
Last updated: Dec 20 2023 at 11:08 UTC