# Miran Lipovača - Learn You a Haskell for Great Good Chapter 9. Input and Output (Highlights) ![rw-book-cover|256](https://readwise-assets.s3.amazonaws.com/static/images/article4.6bc1851654a0.png) ## Metadata **Review**:: [readwise.io](https://readwise.io/bookreview/57995297) **Source**:: #from/readwise #from/reader **Zettel**:: #zettel/fleeting **Status**:: #x **Authors**:: [[Miran Lipovača]] **Full Title**:: Learn You a Haskell for Great Good Chapter 9. Input and Output **Category**:: #articles #readwise/articles **Category Icon**:: 📰 **URL**:: [learnyouahaskell.github.io](https://learnyouahaskell.github.io/input-and-output.html) **Host**:: [[learnyouahaskell.github.io]] **Highlighted**:: [[2026-02-01]] **Created**:: [[2026-02-07]] ## Highlights - That’s because in a *do* block, **the last action cannot be bound to a name** like the first two were. ([View Highlight](https://read.readwise.io/read/01kgcs8zw9a812cevz9p85txc9)) ^983842230 - We also said that in list comprehensions, the *in* part isn’t needed. Well, you can use them in *do* blocks pretty much like you use them in list comprehensions. ([View Highlight](https://read.readwise.io/read/01kgcsdt18p2509mgfrtcz61mz)) ^983842520 - That’s why in an I/O *do* block, *if*s have to have a form of `if *condition* then *I/O action* else *I/O action*.` ([View Highlight](https://read.readwise.io/read/01kgcstypaf3twnagean6awr4h)) ^983845648 - `getChar` is an I/O action that reads a character from the input. Thus, its type signature is `getChar :: IO Char`, because the result contained within the I/O action is a `Char`. Note that due to buffering, reading of the characters won’t actually happen until the user mashes the return key. ([View Highlight](https://read.readwise.io/read/01kgct7svdj9yv2cjw1wtnkb7h)) ^983846458 - `sequence` takes a list of I/O actions and returns an I/O actions that will perform those actions one after the other. ([View Highlight](https://read.readwise.io/read/01kgctc6t4wxkr3v91r1gg0hj4)) ^983846776 - A common pattern with `sequence` is when we map functions like `print` or `putStrLn` over lists. ([View Highlight](https://read.readwise.io/read/01kgctddkmc8j6h4trfsm0qrxh)) ^983846843 - Because mapping a function that returns an I/O action over a list and then sequencing it is so common, the utility functions `mapM` and `mapM_` were introduced. `mapM` takes a function and a list, maps the function over the list and then sequences it. `mapM_` does the same, only it throws away the result later. We usually use `mapM_` when we don’t care what result our sequenced I/O actions have. ([View Highlight](https://read.readwise.io/read/01kgctef8rqhqzh302jm8xd8ja)) ^983847041 - `forM` (located in `Control.Monad`) is like `mapM`, only that it has its parameters switched around. ([View Highlight](https://read.readwise.io/read/01kgcth308jwg0bx9r35byy2xc)) ^983847445 Thus it looks like a usual `for` statement. - The `(\a -> do ... )` is a function that takes a number and returns an I/O action. We have to surround it with parentheses, otherwise the lambda thinks the last two I/O actions belong to it. ([View Highlight](https://read.readwise.io/read/01kgctkaxwm4b9jg73prj88fpz)) ^983847604 - What’s cool about `getContents` is that it does lazy I/O. When we do `foo <- getContents`, it doesn’t read all of the input at once, store it in memory and then bind it to `foo`. No, it’s lazy! It’ll say: *“Yeah yeah, I’ll read the input from the terminal later as we go along, when you really need it!”*. ([View Highlight](https://read.readwise.io/read/01kgctwnbrtpwpn7xrsp2f6kvt)) ^983849283 - Keep in mind that because strings are basically lists, which are lazy, and `getContents` is I/O lazy, it won’t try to read the whole content at once and store it into memory before printing out the capslocked version. Rather, it will print out the capslocked version as it reads it, because it will only read a line from the input when it really needs to. ([View Highlight](https://read.readwise.io/read/01kgcywcman1dv1pk1sxpecz4j)) ^983883577 - This pattern of getting some string from the input, transforming it with a function and then outputting that is so common that there exists a function which makes that even easier, called `interact`. ([View Highlight](https://read.readwise.io/read/01kgcyzexy2gm9yy12nd2vhr7w)) ^983883713 - Even though we made a program that transforms one big string of input into another, it acts like we made a program that does it line by line. ([View Highlight](https://read.readwise.io/read/01kgcz6mbqwmpbqs7banjzbh0w)) ^983885706 - Another way of doing what we just did is to use the `withFile` function, which has a type signature of `withFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a`. ([View Highlight](https://read.readwise.io/read/01kgcza7rj8fa4a6a65f5cv2n1)) ^983886582 - `appendFile` has a type signature that’s just like `writeFile`, only `appendFile` doesn’t truncate the file to zero length if it already exists but it appends stuff to it. ([View Highlight](https://read.readwise.io/read/01kgczq7cm14dh4mrhka9hb4qr)) ^983887840 - We needed to add the `"\n"` to the end of each line because `getLine` doesn’t give us a newline character at the end. ([View Highlight](https://read.readwise.io/read/01kgczr9etmakgvme1c98wj3an)) ^983887902 - You can control how exactly buffering is done by using the `hSetBuffering` function. ([View Highlight](https://read.readwise.io/read/01kgcztafe31zr4y7ratetds8c)) ^983888150 - The `System.Environment` module has two cool I/O actions. One is `getArgs`, which has a type of `getArgs :: IO [String]` and is an I/O action that will get the arguments that the program was run with and have as its contained result a list with the arguments. `getProgName` has a type of `getProgName :: IO String` and is an I/O action that contains the program name. ([View Highlight](https://read.readwise.io/read/01kgd00mmg53vtbagwp8qav2hs)) ^983888552 - So what if we want to flip four coins? Or five? Well, there’s a function called `randoms` that takes a generator and returns an infinite sequence of values based on that generator. ([View Highlight](https://read.readwise.io/read/01kgd0njkrg00pjcx7m0rd4d0a)) ^983891545 - The problem is, if we do that in our real programs, they will always return the same random numbers, which is no good for us. That’s why `System.Random` offers the `getStdGen` I/O action, which has a type of `IO StdGen`. When your program starts, it asks the system for a good random number generator and stores that in a so-called global generator. `getStdGen` fetches you that global random generator when you bind it to something. ([View Highlight](https://read.readwise.io/read/01kgd0yarrfxjx6gcbh639bv6h)) ^983893913 - For this, we can use the `splitAt` function from `Data.List`, which splits a list at some index and returns a tuple that has the first part as the first component and the second part as the second component. ([View Highlight](https://read.readwise.io/read/01kgd0zkr22s4vx4kmeh7572fa)) ^983893950 - Bytestrings are sort of like lists, only each element is one byte (or 8 bits) in size. The way they handle laziness is also different. ([View Highlight](https://read.readwise.io/read/01kgdp2371xd2q22vdqxdh0r3f)) ^983971194 - Bytestrings come in two flavors: strict and lazy ones. Strict bytestrings reside in `Data.ByteString` and they do away with the laziness completely. ([View Highlight](https://read.readwise.io/read/01kgdp2m37pxagaj61gafy42zs)) ^983971211 - The other variety of bytestrings resides in `Data.ByteString.Lazy`. They’re lazy, but not quite as lazy as lists. ([View Highlight](https://read.readwise.io/read/01kgdp3xdzhfq3185xz7jsjs3t)) ^983971273 in chunks - `fromChunks` takes a list of strict bytestrings and converts it to a lazy bytestring. `toChunks` takes a lazy bytestring and converts it to a list of strict ones. ([View Highlight](https://read.readwise.io/read/01kgdp75xa0c862awfp95rf0nm)) ^983971411 - The bytestring version of `:` is called `cons` It takes a byte and a bytestring and puts the byte at the beginning. It’s lazy though, so it will make a new chunk even if the first chunk in the bytestring isn’t full. That’s why it’s better to use the strict version of `cons`, `cons'` if you’re going to be inserting a lot of bytes at the beginning of a bytestring. ([View Highlight](https://read.readwise.io/read/01kgdpagwdmd26db7fkzdx8gyp)) ^983971606 - Despite having expressive types that support failed computations, Haskell still has support for exceptions, because they make more sense in I/O contexts ([View Highlight](https://read.readwise.io/read/01kgdprjf1ttxzb8dcaf4ts0e5)) ^983972372 - Pure code can throw exceptions, but they can only be caught in the I/O part of our code (when we’re inside a *do* block that goes into `main`). ([View Highlight](https://read.readwise.io/read/01kgdpsfw4xvrdmnh5zb35pta7)) ^983972404 - The solution? Don’t mix exceptions and pure code. Take advantage of Haskell’s powerful type system and use types like `Either` and `Maybe` to represent results that may have failed. ([View Highlight](https://read.readwise.io/read/01kgdpwk2vac1vf3kcvdha8723)) ^983972483 - To deal with this by using exceptions, we’re going to take advantage of the `catch` function from `System.IO.Error`. ([View Highlight](https://read.readwise.io/read/01kgdq50cyetbsm7d6y6f33cdc)) ^983973113 - `isDoesNotExistError` is a predicate over `IOError`s, which means that it’s a function that takes an `IOError` and returns a `True` or `False` ([View Highlight](https://read.readwise.io/read/01kgdq98jqxcz1b4mvyjz144x0)) ^983973373 - If it’s not caused by a file not existing, we re-throw the exception that was passed by the handler with the `ioError` function. ([View Highlight](https://read.readwise.io/read/01kgdqa0dvdf1pysjw1n292cxm)) ^983973412