tl;dr: IO
doesn't do anything, it's just a way of describing getting a value from
somewhere
- we should be proud
- some examples of effects:
- printing st stdout (
putStrLn
) - reading from stdin (
getChar
) - modifying state (
ST
)
- printing st stdout (
- why we care about keeping effects separate: there's an implication that order matters in a way that isn't true in pure functions
- "an explanation using van Laarhoven Free Monads and costate comonad coalgebras" -- a joke about how most explanations of this thing way overcomplicate it, which was definitely necessary and helped foster understanding
- goals:
- explain how IO works operationally
- explain how we should read
IO a
- provide more detail on IO's
Functor
,Applicative
, andMonad
instances (but not write them for toy IO types, woooooooo 🎉)
there are a bunch of ways that other explanations go astray. I don't think we should think about them, because I don't think thinking about how other people are wrong is super helpful for learning something new, but if you really want to...
How other people are wrong
- enforce operations in order and turn off some magical sharing
- orders code through a bunch of nested lambdas to disable sharing and enforce order
- we have monads specifically to abstract away this nested lambda noise
values of type
IO a
are not ana
, they're descriptions of how you might get ana
. [...] a description of how you might get that [a
] from the "real world"
- so what should sharing mean in that case?
- why we want sharing to be disabled:
getCurrentTime :: IO UTCTime
; with sharing, we'd always get the same time, sincegetCurrentTime
is non-thunked
we would continue to age, but your program wouldn't work at all the way you'd intended
kill me
-
IO UTCTime
disables sharing though, so every time we call it in a shell, we get the current time instead of the time we called it first -
another example:
whnf
vswhnfIO
(andnf
):whnf :: (a -> b) -> a -> Benchmarkable
whnfIO :: IO a -> Benchmarkable
- the
IO
form doesn't require the function argument
-
another example I think -- fuzzing and unit testing in elm requires wrapping in a lambda, and elm requires GHC, so I think maybe that's what's going on there also: https://github.com/jisantuc/AFDG/blob/develop/afdg-frontend/tests/TileTests.elm#L218-L234
-
an example with
MVar
s and anIO (MVar Int)
-- all of this confusion could have been avoided if we'd calledmyData
getMyData
. Give your variables good names.
- if you bind a value out of IO, that value is shareable
- purity originally meant "semantics are a lambda calculus"
- a function "is referentially transparent when it can be replaced with its value without changing the behavior of a program"
- a function returning an
IO a
is referentially transparent, because given the same inputs, it was always return the sameIO a
-- the same "way to get ana
"
What they each do:
<$>
turns an(a -> b)
and anIO a
into anIO b
<*>*
turns anIO (a -> b)
and anIO a
into anIO b
>>=
sequences anIO a
and ana -> IO b
to get anIO b
Examples:
(+1) <$> randomIO :: IO Int)
lifts our incrementer over theIO
structure -- we'll get a random int and then add 1 to it- we didn't do anything, we just have a new way of getting an
Int
(++) <$> getLine <*> getLine
lets us combine the results of twoIO
actions with a binary function. also(+) <$> (randomIO :: IO Int) <*> (randomIO :: IO Int)
. The first is anIO String
and the second is anIO Int
- the example with
Monad
s andjoin
wasn't good. The lesson seemed to be, if you have anIO
that doesn't seem to be doing anything,join
it
These aren't really... IO
exercises. They're more of an aha! Your previous code! It has IOs in it!
.