Sunday, February 5, 2012

LYAH Chapter 9 - Input and Output


Input and Output
  • Putting I/O actions together with do syntax glues them into one I/O action. 
  • do returns the type of the last IO Action e.g. IO ().
  • Because of that, main always has a type signature of main :: IO something, where something is some concrete type. 
  • By convention, we don't usually specify a type declaration for main.
  • So what's up with name <- getLine then? 
    • You can read that piece of code like this: perform the I/O action getLine and then bind its result value to name
    • getLine has a type of IO String, so name will have a type ofString
    • You can think of an I/O action as a box with little feet that will go out into the real world and do something there (like write some graffiti on a wall) and maybe bring back some data. 
  • You can use  let bindings in do blocks pretty much like you use them in list comprehensions.
    • import Data.Char  
    • main = do  
    •     putStrLn "What's your first name?"  
    •     firstName <- getLine  
    •     putStrLn "What's your last name?"  
    •     lastName <- getLine  
    •     let bigFirstName = map toUpper firstName  
    •         bigLastName = map toUpper lastName  
    •     putStrLn $ "hey " ++ bigFirstName ++ " " ++ bigLastName ++ ", how are you?"  
  • Protip: To run a program you can either compile it and then run the produced executable file by doing ghc --make helloworld and then ./helloworld or you can use the runhaskell command like so: runhaskell helloworld.hs and your program will be executed on the fly.
  • In an I/O do block, ifs have to have a form of if condition then I/O action else I/O action.
  • the return in Haskell is really nothing like the return in most other languages! 
    • it makes an I/O action out of a pure value. If you think about the box analogy from before, it takes a value and wraps it up in a box. 
    • Using return doesn't cause the I/O do block to end in execution or anything like that.
    • return is sort of the opposite to <-. While return takes a value and wraps it up in a box, <- takes a box (and performs it) and takes the value out of it, binding it to a name.
    • When dealing with I/O do blocks, we mostly use return either because we need to create an I/O action that doesn't do anything or because we don't want the I/O action that's made up from a do block to have the result value of its last action
  • do block can also have just one I/O action. 
  • putStr is much like putStrLn in that it takes a string as a parameter and returns an I/O action that will print that string to the terminal, only putStr doesn't jump into a new line after printing out the string while putStrLn does.
  • putChar takes a character and returns an I/O action that will print it out to the terminal.
  • putStr is actually defined recursively with the help of putChar
  • print takes a value of any type that's an instance of Show (meaning that we know how to represent it as a string), calls show with that value to stringify it and then outputs that string to the terminal. Basically, it's just putStrLn . show. It first runs show on a value and then feeds that to putStrLn, which returns an I/O action that will print out our value.
    • When we want to print out strings, we usually use putStrLn (because we don't want the quotes around them), but for printing out values of other types to the terminal, print is used the most.
  • getChar is an I/O action that reads a character from the input. 
  • The when function is found in Control.Monad. It's interesting because in a do block it looks like a control flow statement, but it's actually a normal function. It takes a boolean value and an I/O action if that boolean value is True, it returns the same I/O action that we supplied to it. However, if it's False, it returns thereturn (), action, so an I/O action that doesn't do anything. Here's how we could rewrite the previous piece of code with which we demonstrated getChar by using when:
    • main = do  
    •     c <- getChar  
    •     when (c /= ' ') $ do  
    •         putChar c  
    •         main  
  • sequence takes a list of I/O actions and returns an I/O actions that will perform those actions one after the other. The result contained in that I/O action will be a list of the results of all the I/O actions that were performed. Its type signature issequence :: [IO a] -> IO [a]
  • forever takes an I/O action and returns an I/O action that just repeats the I/O action it got forever. It's located in Control.Monad.
  • forM (located in Control.Monad) is like mapM, only that it has its parameters switched around. The first parameter is the list and the second one is the function to map over that list, which is then sequenced.
    • You can think of forM as meaning: make an I/O action for every element in this list. What each I/O action will do can depend on the element that was used to make the action. Finally, perform those actions and bind their results to something. We don't have to bind it, we can also just throw it away.
  • In this section, we:
    • learned the basics of input and output. 
    • found out what I/O actions are, how they enable us to do input and output and when they are actually performed. 
    • I/O actions are values much like any other value in Haskell. 
    • We can pass them as parameters to functions and functions can return I/O actions as results. 
    • What's special about them is that if they fall into the main function (or are the result in a GHCI line), they are performed. 
    • Each I/O action can also encapsulate a result with which it tells you what it got from the real world.
  • Don't think of a function like putStrLn as a function that takes a string and prints it to the screen. Think of it as a function that takes a string and returns an I/O action. That I/O action will, when performed, print beautiful poetry to your terminal.

No comments:

Post a Comment