lean4-htt/doc/monads/except.lean
Chris Lovett cc95456639
doc: add documentation on monads (#1505)
* doc: add documentation on functors.

* fix: make comments green

* minor tweaks

* doc: add section on Applicatives.

* doc: add some more info on the laws from Mario.

* doc: add law list and move lazy evaluation up so the chapter ends properly.

* doc: Add something on seqLeft and seqRight.

* doc: add section on monads.

* doc: fix some typos.

* doc: switch to LeanInk for chaper on Monads.

* doc: remove old files.

* doc: fix mdbook test errors.

* doc: add part 4: readers

* doc: add section on State monads

* doc: fix some typos and add some more details.

* doc: fix typos and add some CR feedback.

* doc: add Except monad section.

* doc: add info on monad transformers.

* Delete transformers.lean.md

* doc: fix some typos.

* doc: fix typos and move forward reference to monad lifting.

* doc: Update `State` to `StateM`

* doc: fix references to State to become StateM.

* doc: generalize indexOf implementation.

* doc: add chapter on monad laws and move "law" sections to this chapter to avoid redundancy.

* doc: add theorem

* Delete laws.lean.md

* doc: fix some typos.

* doc: fix broken link.

* doc: fox typos.

* fix: language changed from "us" to "you".

* doc: fix code review comments.

* doc: some word smithing

* doc: some word smithing and sample simplification.

* doc: add bad_option_map example.

* doc: add side note on `return` statement and fix heading level consistency.

* Add `withReader` info

* doc: change language from us, our, your, we, we'll, we've to "you"

* doc: add some forward links.

* doc: put spaces around colon in function  arguments like "(x : List Nat)"

* doc:
Add backticks on map
Remove commands on multiline structure instance
Fix centerdot
Add "one of"

* doc: Remove info about Functor in other languages.

* doc:
add info on <$> being left associative
remove another forward reference to monad
fix typo `operatoions`
remove unneccesary parens after <$>

* doc:
fix Type u -> Type v
fix you -> your
use `let val? ← IO.getEnv name`
in Readers call it "context" rather than "state".

* doc: fix withReader docs
use 'context' to describe the ReaderM state.
remove "trivial"
type inference => Lean
"abstract classes" => "abstract structures"
remove unnecessary parens

* doc: fix bug in explanation of `let x ← readerFunc2`
Fix explanation of equivalence between `def f (a : Nat) : String` and `def f : Nat → String`

* doc: move hasSomeItemGreaterThan to Except.lean
Add validateList List.anyM example.
fix `def f (a : Nat) : String` language.

* doc: fix "What transformation are you referring to"

* doc: fix typo.

* doc: add missing period.

* doc: fix validateList

* doc: explain `λ` notation.

* doc: reword the map, seq, bind comparison.

* doc: fix some more 'reader state' to 'reader context' language

* doc: fix wrote statement about return only works in do blocks.

* doc: fix typo

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>

* doc: improve language

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>

* doc: fix typo

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>

* Add info on what a do block is doing for you.

* doc: define definitionally equal

* doc: make `readerFunc3.run env` canonical.

* doc: remove unnecessary parens.

* doc: fix typos

* doc: make List.map a bit more clear in the intro to Functors.

* doc: simplify readerFunc3WithReader

* doc: switch to svg for diagram so it works better on dark themes.

* doc: align nodes in diagram and convert to svg.

* doc: simplify playGame using while true.

* doc: drop confusing statement about "definitionally equal"

* doc: switch to `import Lean.Data.HashMap`

* doc: fix typo "operatoins"

* doc: update diagram to add more info and polish the intro paragraphs so they better match the actual contents of each chapter.

* doc: fix typo

* doc: fix typo.

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2022-09-05 13:33:15 -07:00

166 lines
No EOL
6.3 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-!
# Except
The `Except` Monad adds exception handling behavior to your functions. Exception handling
in other languages like Python or Java is done with a built in `throw` method that you
can use anywhere. In `Lean` you can only `throw` an exception when your function is
executing in the context of an `Except` monad.
-/
def divide (x y: Float): Except String Float :=
if y == 0 then
throw "can't divide by zero"
else
pure (x / y)
#eval divide 5 2 -- Except.ok 2.500000
#eval divide 5 0 -- Except.error "can't divide by zero"
/-!
Just as the `read` operation was available from the `ReaderM` monad and the `get` and `set`
operations came with the `StateM` monad, here you can see a `throw` operation is provided by the
`Except` monad.
So in Lean, `throw` is not available everywhere like it is in most imperative programming languages.
You have to declare your function can throw by changing the type signature to `Except String Float`.
This creates a function that might return an error of type `String` or it might return a value of
type `Float` in the non-error case.
Once your function is monadic you also need to use the `pure` constructor of the `Except` monad to
convert the pure non-monadic value `x / y` into the required `Except` object. See
[Applicatives](applicatives.lean.md) for details on `pure`.
Now this return typing would get tedious if you had to include it everywhere that you call this
function, however, Lean type inference can clean this up. For example, you can define a test
function can calls the `divide` function and you don't need to say anything here about the fact that
it might throw an error, because that is inferred:
-/
def test := divide 5 0
#check test -- Except String Float
/-!
Notice the Lean compiler infers the required `Except String Float` type information for you.
And now you can run this test and get the expected exception:
-/
#eval test -- Except.error "can't divide by zero"
/-!
## Chaining
Now as before you can build a chain of monadic actions that can be composed together using `bind (>>=)`:
-/
def square (x : Float) : Except String Float :=
if x >= 100 then
throw "it's absolutely huge"
else
pure (x * x)
#eval divide 6 2 >>= square -- Except.ok 9.000000
#eval divide 6 0 >>= square -- Except.error "can't divide by zero"
#eval divide 100 1 >>= square -- Except.error "it's absolutely huge"
def chainUsingDoNotation := do
let r ← divide 6 0
square r
#eval chainUsingDoNotation -- Except.error "can't divide by zero"
/-!
Notice in the second `divide 6 0` the exception from that division was nicely propagated along
to the final result and the square function was pretty much ignored in that case.
Remember also that you can chain the actions with implicit binding by using the `do` notation
as you see in the `chainUsingDoNotation` function above.
## Try/Catch
Now with all good exception handling you also want to be able to catch exceptions so your program
can try continue on or do some error recovery task, which you can do like this:
-/
def testCatch :=
try
let r ← divide 8 0 -- 'r' is type Float
pure (toString r)
catch e =>
pure s!"Caught exception: {e}"
#check testCatch -- Except String String
/-!
Note that the type inferred by Lean for this function is `Except String String` so unlike the
`test` function earlier, this time Lean type inference has figured out that since the pure
value `(toString r)` is of type `String`, then this function must have type `Except String String`
so you don't have to explicitly state this. You can always hover your mouse over `testCatch`
or use `#check testCatch` to query Lean interactively to figure out what type inference
has decided. Lean type inference makes life easy for you, so it's good to use it
when you can.
You can now see the try/catch working in this eval:
-/
#eval testCatch -- Except.ok "Caught exception: can't divide by zero"
/-!
Notice the `Caught exception:` wrapped message is returned, and that it is returned as an
`Except.ok` value, meaning `testCatch` eliminated the error result as expected.
So you've interleaved a new concept into your functions (exception handling) and the compiler is still
able to type check everything just as well as it does for pure functions and it's been able to infer
some things along the way to make it even easier to manage.
Now you might be wondering why `testCatch` doesn't infer the return type `String`? Lean does this as a
convenience since you could have a rethrow in or after the catch block. If you really want to stop
the `Except` type from bubbling up you can unwrap it like this:
-/
def testUnwrap : String := Id.run do
let r ← divide 8 0 -- r is type Except String Float
match r with
| .ok a => toString a -- 'a' is type Float
| .error e => s!"Caught exception: {e}"
#check testUnwrap -- String
#eval testUnwrap -- "Caught exception: can't divide by zero"
/-!
The `Id.run` function is a helper function that executes the `do` block and returns the result where
`Id` is the _identity monad_. So `Id.run do` is a pattern you can use to execute monads in a
function that is not itself monadic.
## Monadic functions
You can also write functions that are designed to operate in the context of a monad.
These functions typically end in upper case M like `List.forM` used below:
-/
def validateList (x : List Nat) (max : Nat): Except String Unit := do
x.forM fun a => do
if a > max then throw "illegal value found in list"
#eval validateList [1, 2, 5, 3, 8] 10 -- Except.ok ()
#eval validateList [1, 2, 5, 3, 8] 5 -- Except.error "illegal value found in list"
/-!
Notice here that the `List.forM` function passes the monadic context through to the inner function
so it can use the `throw` function from the `Except` monad.
The `List.forM` function is defined like this where `[Monad m]` means "in the context of a monad `m`":
-/
def forM [Monad m] (as : List α) (f : α → m PUnit) : m PUnit :=
match as with
| [] => pure ⟨⟩
| a :: as => do f a; List.forM as f
/-!
## Summary
Now that you know all these different monad constructs, you might be wondering how you can combine
them. What if there was some part of your state that you wanted to be able to modify (using the
State monad), but you also needed exception handling. How can you get multiple monadic capabilities
in the same fuunction. To learn the answer, head to [Monad Transformers](transformers.lean.md).
-/