3 ms·
You can also use it for representing errors, for representing global read-only variables, for representing global write-only variables, for representing non-det
by trealira 2y ago
You can also use it for representing errors, for representing global read-only variables, for representing global write-only variables, for representing non-determinism, for representing continuations, and other things (since you can write your own).
For example, this is a really minimalistic arithmetic syntax tree interpreter in Haskell. You could write errors using the Either type:
data Term = Constant Int | Divide Term Term
eval :: Term -> Either String Int
eval (Constant n) = n
eval (Divide left right) =
case eval left of
Left errorString -> Left errorString
Right leftResult ->
case eval right of
Left errorString -> Left errorString
Right rightResult ->
if rightResult == 0
then Left "Division by zero"
else Right (div leftResult rightResult)
It's cumbersome, and there's a staircase growing. Because Either is a monad, you could rewrite the division op like this:
eval (Divide left right) = do
leftResult <- eval left
rightResult <- eval right
if rightResult == 0
then Left "Division by zero"
else Right (div leftResult rightResult)
This is a good article about the either monad: https://mmhaskell.com/blog/2022/3/3/using-either-as-a-monad https://mmhaskell.com/blog/2022/3/3/using-either-as-a-monad
The state monad is basically a wrapper over a pure function that takes a state and returns a tuple with the result and a new state. In an imperative language, you'd just actually change state, rather than doing that.
An example from this wiki page: https://en.wikibooks.org/wiki/Haskell/Understanding_monads/State#Pseudo-Random_Numbers https://en.wikibooks.org/wiki/Haskell/Understanding_monads/S...
In an imperative language, for pseudo-random numbers, you could modify global variables every time you want a new random number. Haskell lets you do this in IO functions. But a pure way would be to return the result of the new global variable every time.
-- randomR takes a range, so randomR (1,6) produces a random number between 1 and 6
rollPair :: StdGen -> ((Int, Int), StdGen)
rollPair s0 =
let (r1, s1) = randomR (1,6) s0
(r2, s2) = randomR (1,6) s1
in ((r1, r2), s2)
It's annoying. You could use the state monad to simplify this:
rollDieS :: State StdGen Int
rollDieS = state (randomR (1,6))
rollPairS :: State StdGen (Int, Int)
rollPairS = do
r1 <- rollDieS
r2 <- rollDieS
return (r1, r2)
- darby_nine 2y ago> The state monad is basically a wrapper over a pure function that takes a state and returns a tuple with the result and a new state. So... it's a monad? I don't understand why this is worth calling State as distinct from non-State when that doesn't seem to add any meaning. Surely there must be something implied by a State monad that is not implied by a non-State monad. That seems relevant to call out in an introduction.
- trealira 2y agoI don't see why you think all monads are just like the State monad. The Maybe and Either monads have nothing to do with state, for example.
- darby_nine 2y ago> The Maybe and Either monads have nothing to do with state, for example. See this is what I'm talking about. What does `state` mean to you where monads don't have any relation? It's just an abstract structure—what makes something state or not is the context.
- trealira 2y agoState can be modeled as a monad, but monads aren't a synonym for state. Just like squares are technically rectangles, but "rectangle" isn't a synonym for "square." How do I think of the State monad? - Like a Mealy machine: https://en.wikipedia.org/wiki/Mealy_machine https://en.wikipedia.org/wiki/Mealy_machine - Like a pure way to compose functions that use a nameless, global mutable variable. - As a newtype wrapper over the type "s -> (a, s)" where s is the type of the state and a is the type of the computation.
- deleted 2y ago[deleted]
- trealira 2y agoJust realized I should have written one of these for the Constant evaluation: eval (Constant n) = Right n eval (Constant n) = return n