3 ms·
IO distinguishes execution (of actions) from evaluation (of expressions). To execute an `action :: IO Ty' for a value of `a :: Ty', you use <- do (a :: Ty)
by Iceland_jack 3y ago
IO distinguishes execution (of actions) from evaluation (of expressions).
To execute an `action :: IO Ty' for a value of `a :: Ty', you use <-
do (a :: Ty) <- (action :: IO Ty)
..
The 'function' rand is not really a function, but an action. It doesn't make sense to ask which int rand() evaluates to, and we can't equationally reason about rand() as an int. We cannot factor it from `rand() + rand()', or replace it with `2 * rand()' because it is not an int! Haskell is explicit about this, `rand :: IO Int' an action that produces an Int when (effectfully) executed.
The addition of actions doesn't make sense `rand + rand': Num-eric operations do not automatically lift over IO Int.[1] Instead we explicitly write `liftA2 (+) rand rand'. Shorthand for
do r1 <- rand
r2 <- rand
pure (r1 + r2)
where r{1,2} are Ints. The separation between evaluation and execution means we can factor rand out while still executing it twice.
do let r :: IO Int
r = rand
r1 <- r
r2 <- r
pure (r1 + r2)
This factors out the 'recipe', not the value it produces. To factor out the result of the IO-action, we just use a single bind/draw <-.
do r1 <- rand
pure (r1 + r1)
[1] This can be changed with Applicative lifting:
{-# Language DerivingVia #-}
deriving via Ap IO a
instance Num a => Num (IO a)
- Iceland_jack 3y agoSee "Tackling the Awkward Squad: monadic input/output, concurrency, exceptions, and foreign-language calls in Haskell" by Simon Peyton Jones https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/mark.pdf https://www.microsoft.com/en-us/research/wp-content/uploads/...