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
- A 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