|CS 331 Spring 2013 > Lecture Notes for Wednesday, February 27, 2013|
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
to a string, and then sends it to
In Haskell, string conversion and I/O are kept a bit more separate. We look first at string conversion
show converts pretty much anything to
This function is overloaded for any type in class
[Interactive Haskell]> show 345 "345" > length (show 345) -- length of String 3 > length 345 -- 345 is not a String ERROR ...
read can convert a
to pretty much anything.
This function is overloaded for any type in class
[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
[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
This is defined as follows.
[Haskell]asTypeOf :: a -> a -> a asTypeOf x _ = x
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
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!
putStr takes a
putStr returns an I/O action.
When returned to the outside world,
this I/O action causes the string to be printed.
putStrLn is the same,
except that it adds a newline on the end.
[Interactive Haskell]> :t putStr putStr :: String -> IO ()
is the same as
putStrLn (show x).
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
[Haskell]printMessages2 = do putStrLn "My" do putStrLn "dog" putStrLn "has" putStrLn "fleas."
[Interactive Haskell]> :t printMessages2 printMessages2 :: IO () > printMessages2 My dog has fleas.
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
String is read from the standard input.
Two important points:
All of the above I/O actions involved a non-trivial side effect.
We can wrap a value in a do-nothing side effect
For example, “
produces a do-nothing I/O action that wraps the value
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
do block is a single expression.
Also note that Haskell’s “
does 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,
However, in practice
return is almost always
the last I/O action performed.
Thus the name “
One last bit of syntax remains.
We can use “
let ... = ...”
to bind a name to a non-I/O value,
for the remainder of the
[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.
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.
doblock combines multiple I/O actions into a single I/O action.
<-” to bind a variable name to a wrapped value, in the context of a
returnto create a do-nothing I/O action wrapping a specified value.
doblock to bind a variable name to a non-I/O value.
for Haskell source code related to today’s lecture.