首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

「学习札记」Chapter 8 INPUT AND OUTPUT in Haskell

2012-12-31 
「学习笔记」Chapter 8 INPUT AND OUTPUT in HaskellChapter 8 INPUT AND OUTPUT in HaskellIn this chapter

「学习笔记」Chapter 8 INPUT AND OUTPUT in Haskell

Chapter 8 INPUT AND OUTPUT in Haskell

In this chapter, you’re going to learn how to receive input from the keyboard and print stuff to the screen.

Table of Contents1 Separating the Pure from the Impure2 Hello, World!3 Gluing I/O Actions Together3.1 Using let Inside I/O Actions3.2 Putting It in Reverse4 Some Useful I/O Functions4.1 putStr4.2 putChar4.3 print4.4 when4.5 sequence4.6 mamM4.7 forever4.8 forM5 I/O Action Review

When we were making a binary search tree in the previous chapter, we didn’t insert an element into a tree by modifying the tree itself; instead, our function actually returned a new tree with the new element inserted into that.

The fact that functions cannot change state—like updating global variables, for example—is good, because it helps us reason about our programs. However, there’s one problem with this: If a function can’t change anything in the world, how is it supposed to tell us what it calculated? To do that, it must change the state of an output device (usually the state of the screen).

Haskell has a really clever system for dealing with functions that have side effects. It neatly separates the part of our program that is pure and the part of our program that is impure, which does all the dirty work like talking to the keyboard and the screen. With those two parts separated, we can still reason about our pure program and take advantage of all the things that purity offers—like laziness, robustness, and composability—while easily communicating with the outside world.

First, we write a hello, world. Write the following in your favourite editor, and save it as a file like helloworld.hs.

main = putStrLn "Hello, world"

Then, compile it in terminal

$ ghc helloword.hs[1 of 1] Compiling Main             ( chap8.hs, chap8.o )Linking helloword ...

Now, you can Run it!

$ ./helloworldHello, world

Now, let's return to GHCI, you can run it in another way.

ghci>:l helloword.hs[1 of 1] Compiling Main             ( chap8.hs, interpreted )Ok, modules loaded: Main.ghci>mainHello, world

Let's analysis the program we wrote. First, we look at type of the function putStrLn.

ghci>:t putStrLnputStrLn :: String -> IO ()ghci>:t putStrLn "Hello, world"putStrLn "Hello, world" :: IO ()

We can read the type of putStrLn like this: putStrLn takes a string and returns an I/O action that has a result type of () that is, the empty tuple, also known as unit).

we can use do syntax to glue together several I/O actions into one. Take a look at the following example:

main = do  putStrLn "What's your name?"  name <- getLine  putStrLn ("Nice to meet you " ++ name ++ ".")

After compile, we can run it.

$ ./chap8 What's your name?JK Nice to meet you JK.

Notice that we said do and then we laid out a series of steps, as we would in an imperative program. Each of these steps is an I/O action. By putting them together with do syntax, we glued them into one I/O action. The ac- tion that we got has a type of IO (), as that’s the type of the last I/O action inside. Because of that, main always has a type signature of main :: IO something, where something is some concrete type.

Look at the getLine.

ghci>:t getLinegetLine :: IO String

We see that getLine is an I/O action that yields a String. That makes sense, because it will wait for the user to input something at the terminal, and then that something will be represented as a string.

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 of String.

To see how normal values differ from I/O actions, consider the following line. Is it valid?

nameTag = "Hello, my name is " ++ getLine

This doesn’t work because ++ requires both its parameters to be lists over the same type. The left parameter has a type of String (or [Char], if you will), while getLine has a type of IO String. Remember that you can’t concatenate a string and an I/O action. First, you need to get the result out of the I/O action to get a value of type String, and the only way to do that is to do something like name <- getLine inside some other I/O action.

Notice that in a do block, the last action cannot be bound to a name. do block automatically extracts the value from the last action and yields that as its own result.

What do you think will happen when we do something like the following?

myLine = getLine

Do you think it will read from the input and then bind the value of that to name? Well, it won’t. All this does is give the getLine I/O action a different name called myLine.

Remember that to get the value out of an I/O action, you must perform it inside another I/O action by binding it to a name with <- .

I/O actions will be performed when they are given a name of main or when they’re inside a bigger I/O action that we composed with a do block. We can also use a do block to glue together a few I/O actions, and then we can use that I/O action in another do block, and so on. They will be per- formed if they eventually fall into main.

When using do syntax to glue together I/O actions, we can use let syntax to bind pure values to names. Whereas <- is used to perform I/O actions and bind their results to names, let is used when we just want to give names to normal values inside I/O actions.

Let's look at an example.

import Data.Charmain = do  putStrLn "What's you name?"  name <- getLine  let upperName = map toUpper name  putStrLn $ "Oh, your name is " ++ upperName ++ "."

Test it!

$ ./chap8 What's you name?KayOh, your name is KAY.

You may be wondering when to use <- and when to use let bindings. <- is for performing I/O actions and binding their results to names. map toUpper firstName, however, isn’t an I/O action—it’s a pure expression in Haskell.

To get a better feel for doing I/O in Haskell, let’s make a simple program that continuously reads a line and prints out the same line with the words re- versed. The program’s execution will stop when we input a blank line.

main = do  line <- getLine  if null line     then return ()    else do    putStrLn $ reverseWords line  mainreverseWords :: String -> StringreverseWords  = unwords . map reverse . words

Let's compile and run it!

$ ./chap8 Hi, Jack,iH kcaJHi, Mary, What's wrong with you,iH ,yraM s'tahW gnorw htiw uoy

Our if says that when a condition is true (in our case, the line that we entered is blank), we perform one I/O action; when it isn’t true, the I/O action under the else is performed.Because we need to have exactly one I/O action after the else, we use a doblock to glue together two I/O actions into one.

Inside the do block, we apply reverseWords to the line that we got from getLine and then print that to the terminal. After that, we just perform main. It’s performed recursively, and that’s okay, because main is itself an I/O action. So in a sense, we go back to the start of the program.

return in Haskell is nothing like the return in most other languages. In Haskell (and in I/O actions specifically), return makes an I/O action out of a pure value.

Unlike in other languages, using return doesn’t cause the I/O do block to end in execution. For instance, this program will quite happily continue all the way to the last line.

main = do  return ()  return "HaHa"  line <- getLine  return "BASDF"  return 4  putStrLn line

compile and test it!

$ ./chap8 Fei~Fei~

Notice that "HaHa","BASDF" and 4 does not be output. all these uses of return do is make I/O actions that yield a result, which is then thrown away because it isn’t bound to a name.

If you want to output them, you should bind the result of a name.

main = do--  a <- return ()  b <- return "HaHa"  line <- getLine  c <- return "BASDF"--  d <- return 4  putStrLn $ b ++ line ++ c -- ++ d

Notice that () and 4 don't have the type String, we can't putStrLn them with ++.

Let's look at the result

$ ./chap8 JayHaHaJayBASDF

So you see, return is sort of the opposite of <-. 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.

But you can implement the same function in a directly way.

main = do  let b = "HaHa"      c = "BASDF"  line <- getLine  putStrLn $ b ++ line ++ c 

putStr is much like putStrLine,except that it doesn’t jump into a new line after printing out the string. Let's look at a sample code.

main = do  putStr "hi,jack"  putStr "hi,rose"

Compile and test it.

$ ./chap8 hi,jackhi,rose$

The putChar function takes a character and returns an I/O action that will print it to the terminal. Let's look at a sample code.

main = do  putChar 'a'  putChar 'b'

Compile and test it.

$./chap8ab$

print takes a value of any type that’s an instance of Show.

Let's look at a sample code.

main = do  print "sdf"  print 1  print [1,2,3]  print 3.4

Compile and test it.

$ ./chap8 "sdf"1[1,2,3]3.4

when takes a Bool and an I/O action, and if that Bool value is True, it returns the same I/O action that we supplied to it. However, if it’s False, it returns the return () action, which doesn’t do anything.

Let's look at a sample code.

import Control.Monadmain = do  pwd <- getLine  when (pwd == "haha") $ do    putStrLn "login"

Compile and test it.

$ ./chap8 hahalogin

You may consider that what's the difference between when and if. Let's look at another version.

main = do  pwd <- getLine  if (pwd == "haha")     then putStrLn "login"    else    return ()

Now, you can understand the difference.

The sequence function takes a list of I/O actions and returns an I/O action that will perform those actions one after the other.

Let's look at a sample code.

main = do  rs <- sequence [getLine,getLine,getLine]  print rs

Compile and test it.

$ ./chap8 34"sdfa"[1,2,4]["34","\"sdfa\"","[1,2,4]"]

sequence can transform a list of I/O actions to an I/O action.

ghci>sequence $ map print [1,2,3]123[(),(),()]

First,executing map print [1,2,3,4] won’t create an I/O action, but instead will create a list of I/O actions like

[print 1, print 2, print 3, print 4]

Second,*sequence* put these I/O actions together.

At last, what's the [(),(),()] means? When we evaluate an I/O action in GHCi, that action is performed, and then its result is printed out, unless that result is ().

when we enter getLine in GHCi, the result of that I/O action is printed out, because getLine has a type of IO String.

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.

Let's look at a sample.

ghci>mapM print [1,2,3]123[(),(),()]ghci>mapM_ print [1,2,3]123

The forever function takes an I/O action and returns an I/O action that just repeats the I/O action it got forever.

Let's look at a sample code.

import Control.Monadmain = forever $  do  putStrLn "What's your name:"  l <- getLine  putStrLn $ "Oh," ++ l ++"."

Compile and test it.

$ ./chap8 What's your name:KayOh,Kay.What's your name:MayOh,May.What's your name:

forM (located in Control.Monad) is like mapM, but its parameters are switched around. The first parameter is the list, and the second is the function to map over that list, which is then sequenced.

Let's look at a sample code.

import Control.Monadmain = do  colors <- forM [1,2,3,4] (\a -> do                               putStrLn $ "What's you favourite color " ++ show a ++"?"                               color <- getLine                               return color                           )  putStrLn "The color you are associate with 1,2,3,4 are:"  mapM putStrLn colors

Compile and test it.

$ ./chap8 What's you favourite color 1?BlueWhat's you favourite color 2?Black    What's you favourite color 3?RedWhat's you favourite color 4?GreenThe color you are associate with 1,2,3,4 are:BlueBlackRedGreen

Normally, we use forM when we want to map and sequence some actions that we define on the spot using do notation.

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 I/O actions 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 yield a result to tell you what it got from the real world.


热点排行