CS 331 Spring 2013  >  Lecture Notes for Wednesday, February 27, 2013

CS 331 Spring 2013
Lecture Notes for Wednesday, February 27, 2013

Haskell: I/O

String Conversion

Strings & I/O

In many languages, conversion to & from a string is mixed up together with I/O. This makes sense, because when we do text I/O, the values we send and receive must, in the end, be composed of characters. For example, in C++:

[C++]

int x;
cout << x;

The second line above converts the value of x to a string, and then sends it to cout.

In Haskell, string conversion and I/O are kept a bit more separate. We look first at string conversion

Function show

Function show converts pretty much anything to a String. This function is overloaded for any type in class Show.

[Interactive Haskell]

> show 345
"345"
> length (show 345)  -- length of String
3
> length 345         -- 345 is not a String
ERROR ...

Function read

Function read can convert a String to pretty much anything. This function is overloaded for any type in class Read.

[Interactive Haskell]

> read "345"
ERROR ...

We may need to indicate the type to convert to. Often the compiler can determine this by looking at what is done with the return value of read.

[Interactive Haskell]

> 2 + read "345"    -- Integer context
347
> 2.0 + read "345"  -- Double context
347.0

An interesting way to specify a type requirement is to use function asTypeOf. This is defined as follows.

[Haskell]

asTypeOf :: a -> a -> a
asTypeOf x _ = x

So asTypeOf returns its first argument, but requires this to be the same type as its second argument.

[Interactive Haskell]

> (read "345") `asTypeOf` 12    -- Convert to Integer
345
> (read "345") `asTypeOf` 12.0  -- Convert to Double
345.0

Simple Output

I/O Actions

An I/O action is a type of value. We do I/O by returning an I/O action to the outside world.

[Haskell]

sayHowdyNewLine = putStrLn "Howdy!"

[Interactive Haskell]

> :t sayHowdyNewLine
sayHowdyNewLine :: IO ()
> sayHowdyNewLine
Howdy!

Function putStr takes a String parameter and putStr returns an I/O action. When returned to the outside world, this I/O action causes the string to be printed. Function putStrLn is the same, except that it adds a newline on the end.

[Interactive Haskell]

> :t putStr
putStr :: String -> IO ()

Saying print x is the same as putStrLn (show x).

do Blocks

A do block combines multiple I/O actions into a single I/O action.

[Haskell]

printMessages = do
    putStrLn "Hello"
    putStr "Here is a number: "
    print (7*8 + 15)
    putStrLn "Bye!"

[Interactive Haskell]

> :t printMessages
printMessages :: IO ()
> printMessages
Hello
Here is a number: 71
Bye!

We can nest do blocks.

[Haskell]

printMessages2 = do
    putStrLn "My"
    do
        putStrLn "dog"
        putStrLn "has"
    putStrLn "fleas."

[Interactive Haskell]

> :t printMessages2
printMessages2 :: IO ()
> printMessages2
My
dog
has
fleas.

Simple Input

An I/O action is essentially a side effect wrapping a value. The above I/O actions all wrap “nothing” values. We do input using an I/O action that wraps a non-trivial value.

[Interactive Haskell]

> :t getChar
getChar :: IO Char
> :t getLine
getLine :: IO String

We can bind a variable name to the wrapped value by using “<-”.

[Haskell]

reverseIt = do
    putStr "Type something: "
    line <- getLine
    putStr "You typed (backwards): "
    putStrLn (reverse line)

Above, the name line is bound to the String wrapped by the I/O action that getLine returns. This String is read from the standard input.

Two important points:

Using return in a do Block

All of the above I/O actions involved a non-trivial side effect. We can wrap a value in a do-nothing side effect using return. For example, “return x” produces a do-nothing I/O action that wraps the value x. And “return ()” produces a do-nothing I/O action that wraps a “nothing” value—essentially a null I/O action.

[Haskell]

-- myGetLine
-- Same as getLine, but showing how to write it using getChar.
myGetLine = do
    c <- getChar
    if c == '\n'
        then return ""
        else do
            rest <- myGetLine
            return (c:rest)

Note that each expression in a do block needs to return an I/O action. However, these expressions can be complicated. For example, from the above if to the end of the do block is a single expression.

Also note that Haskell’s “returndoes not return. It simply creates a do-nothing I/O action. There is nothing to prevent us from doing several return expressions in a row, following by—say—a putStrLn. (Try it!) However, in practice a return is almost always the last I/O action performed. Thus the name “return” makes sense.

Using let in a do Block

One last bit of syntax remains. We can use “let ... = ...” in a do block to bind a name to a non-I/O value, for the remainder of the do block.

[Haskell]

-- squareEm
-- Repeatedly input a number from the user. If 0, then quit; otherwise
--  print its square, and repeat.
squareEm = do
    putStr "Type a number (0 to quit): "
    line <- getLine    -- Bind name to I/O-wrapped value
    let n = read line  -- Bind name to non-I/O value
                       -- Compiler knows n is a number by how it is used
    if n == 0
        then return () -- Must have I/O action here, so make it null
        else do
            putStr "Squaring, we get: "
            print (n*n)
            squareEm   -- repeat

Above, the “then” portion of the expression must return an I/O action; otherwise we have a type error. However, at this point we simply want to leave, so there is nothing for this I/O action to do. Thus we use return () to create a null I/O action.

Quick Summary of Haskell I/O

See haskell_io.hs for Haskell source code related to today’s lecture.


CS 331 Spring 2013: Lecture Notes for Wednesday, February 27, 2013 / Updated: 27 Feb 2013 / Glenn G. Chappell / ggchappell@alaska.edu