Skip to content

Instantly share code, notes, and snippets.

@jisantuc
Created February 20, 2018 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jisantuc/8c807c1495ec884e5da710791fe25f5d to your computer and use it in GitHub Desktop.
Save jisantuc/8c807c1495ec884e5da710791fe25f5d to your computer and use it in GitHub Desktop.

Chapter 29 - IO

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)
  • 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, and Monad instances (but not write them for toy IO types, woooooooo 🎉)

Where IO Explanations Go Astray

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
  • Explanations based on state are wrong because they're less helpful than they seem, since the state isn't like `State` or `StateT` or `ST`. If you go down this path you'll end up at `State#` and an explanation including `RealWorld` is deeply magical, and you don't want to end up there
  • ...apparently that was the only wrong explanation we're going to talk about
  • Why we need the IO type

    • 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

    Sharing

    values of type IO a are not an a, they're descriptions of how you might get an a. [...] 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, since getCurrentTime 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 vs whnfIO (and nf):

      • 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 MVars and an IO (MVar Int) -- all of this confusion could have been avoided if we'd called myData getMyData. Give your variables good names.

    IO doesn't disable sharing for everything

    • if you bind a value out of IO, that value is shareable

    Purity is losing meaning

    • 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 same IO a -- the same "way to get an a"

    IO's Functor, Applicative, and Monad

    What they each do:

    • <$> turns an (a -> b) and an IO a into an IO b
    • <*>* turns an IO (a -> b) and an IO a into an IO b
    • >>= sequences an IO a and an a -> IO b to get an IO b

    Examples:

    • (+1) <$> randomIO :: IO Int) lifts our incrementer over the IO 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 two IO actions with a binary function. also (+) <$> (randomIO :: IO Int) <*> (randomIO :: IO Int). The first is an IO String and the second is an IO Int
    • the example with Monads and join wasn't good. The lesson seemed to be, if you have an IO that doesn't seem to be doing anything, join it

    Chapter Exercises

    These aren't really... IO exercises. They're more of an aha! Your previous code! It has IOs in it!.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment