|
|
|
|
|
by Iceland_jack
851 days 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)
|
|
https://www.microsoft.com/en-us/research/wp-content/uploads/...