4 ms·
The article is very good. However, presenting this stuff in those languages makes it so obtuse. There's a reason this is usually explained in Haskell or in re
by dannymi 4y ago
The article is very good.
However, presenting this stuff in those languages makes it so obtuse.
There's a reason this is usually explained in Haskell or in regular mathematics. There's SO much noise otherwise.
3.4.1 is basically the definition of a (not "the") monad. But look at it! Still so much noise.
Personally, the only explanation I would need is this:
- Often, you need some state to be mutated as a matter of course while you are doing something in steps that you actually care about. But you don't want that state to be in-your-face.
- Therefore, create an abstraction "monad" that hides the state mangling, giving you the illusion that it's not there and the state just mangles itself (compare OOP).
- Then, have an operator to sequence state mangling operations. I'd just call that operator ";" from the C operator of the same name and similar sequencing effect.
- Also, unfortunately, you need a constructor to make a state mangling operation from a non-state-mangling-thing you otherwise have. This is necessary and the only reason you have to be aware of any of this stuff instead of your language being auto-monadifying (the latter you do not want in general for other reasons).
For example when you do writes to the console, it naturally depends on the order of evaluation what your console will look like in the end. But if ";" was not sequencing as it is (in C, too!), your compiler would be free to reorder your write commands however it damn feels like. So the semicolon is an active "thing" and not weird syntactic noise like C programmers like to pretend it is (and then they muddled the waters using it also for things that are not sequencing at all).
So how far does it extend? Well, for an IO monad, as long as you do IO operations, you have to be mindful not to reorder operands of the ";", or even evaluate the right-hand operand first (that would be bad--usually prevented by the right-hand operator being a function (of the respective state) still).
So a monad is a programmable semicolon together with the obvious laws you'd want for algebraic manipulation. That's all.
Now the only remaining question is how do you chain multiple things that need semicolons?
One possible and very popular way to do that is:
write "hello" ;\_ ->
write "world" ;\_ ->
lift 42
that makes an operation that would write "helloworld" and then return 42 to the caller ("lift" being the monad constructor here--like Return in the article).
The "\" marks a lambda. The "->" separates the parameter from the body. Note that each body goes from the right of the respective "->" to the very end (after lift 42).
The names of the formal parameters here are "_" as tradition for "don't care"--but there's no special meaning of "_" at all for the calculus.
More interesting example:
write "What is your name? " ;\_ ->
read ;\name ->
lift name
Or, equivalently (using one of the monad laws in order to simplify it):
write "What is your name? " ;\_ ->
read
This is kinda annoying to write, so you can introduce "do" notation (as a simple syntax-rules macro; or like "error_checked" in the article) so it's (very slightly) nicer to write. But of course that obtuses how it works.
The ";" is just the name of a regular function that you can define (per monad) and it will do stuff. Same for the "lift".
But neat that it works in other formerly-imperative languages now and actually looks halfway decent.
Definitions of ";" can be many and it depends on what you are trying to solve, but an easy-to-understand one is this ("a" and "b" are the two operands passed to ";"):
\a -> \b ->
\state ->
let (result, new_state) = (a state) in
b result new_state
(to compare: the article has the semicolon abort if new_state != ok)
and the "lift" can be:
\a ->
(\state -> (a, state))
and "write"'s signature would be
\a -> \state -> ...
the result would be a pair of the value and the new state.
and "read"'s signature would be
\state -> ...
the result would be a pair of the value and the new state.