Monadic I/O in Haskell Jim Royer CIS 352 March 5, 2019 Jim - - PowerPoint PPT Presentation

monadic i o in haskell
SMART_READER_LITE
LIVE PREVIEW

Monadic I/O in Haskell Jim Royer CIS 352 March 5, 2019 Jim - - PowerPoint PPT Presentation

Monadic I/O in Haskell Jim Royer CIS 352 March 5, 2019 Jim Royer Monadic I/O in [1ex] Haskell 1 / 33 References Chapter 18 of Haskell: the Craft of Functional Programming by Simon Thompson, Addison-Wesley, 2011. Chapter 9 of Learn you a


slide-1
SLIDE 1

Monadic I/O in Haskell

Jim Royer

CIS 352

March 5, 2019

Jim Royer Monadic I/O in [1ex] Haskell 1 / 33

slide-2
SLIDE 2

References

Chapter 18 of Haskell: the Craft of Functional Programming by Simon Thompson, Addison-Wesley, 2011. Chapter 9 of Learn you a Haskell for Great Good by Miran Lipovaˇ ca

http://learnyouahaskell.com/input-and-output

“Tackling the Awkward Squad,” by Simon Peyton Jones

http://research.microsoft.com/~simonpj/papers/marktoberdorf/

Tutorials/Programming Haskell/String IO https://wiki.haskell.org/Tutorials/Programming_Haskell/String_IO Software Tools in Haskell https://crsr.net/Programming_Languages/SoftwareTools/

Jim Royer Monadic I/O in [1ex] Haskell 2 / 33

slide-3
SLIDE 3

Digression: Creating stand-alone Haskell Programs

The program should⋆ have a module called Main, containing a function called main:

module Main where main :: IO () main = (...)

⋆ The first line can be omitted, since the default module name is Main.

Here is a complete example: . . .

Jim Royer Monadic I/O in [1ex] Haskell 3 / 33

slide-4
SLIDE 4

Digression, continued

module Main where main :: IO () main = printStrLn "Hello, world!" [Post:pl/code/IO] jimroyer% ❝❛t ❤❡❧❧♦✳❤s module Main where main :: IO () main = putStrLn "Hello, world!" [Post:pl/code/IO] jimroyer% ❣❤❝ ✲✲♠❛❦❡ ❤❡❧❧♦✳❤s [Post:pl/code/IO] jimroyer% ✳✴❤❡❧❧♦ Hello, world!

putStrLn :: String -> IO () – prints a string to output. How do we create our own IO actions?

Jim Royer Monadic I/O in [1ex] Haskell 4 / 33

slide-5
SLIDE 5

The conflict

Haskell is pure.

Evaluating a Haskell expression just produces a value. It does not change anything! Ghci, not Haskell, handles printing results.

But the point of a program is to interact with the world — if only at the level of input & output.

∴ Doing input/output in Haskell requires a new idea.

Jim Royer Monadic I/O in [1ex] Haskell 5 / 33

slide-6
SLIDE 6

Monadic I/O

An I/O action has a type of the form (IO a). (a, a type param.) An expression of type (IO a) produces an action. When this action is performed:

it may do some input/output, and finally produces a value of type a.

Roughly: IO a ≈ World -> (a, World)

IO a

World out World in result::a

(Pictures from SPJ.)

Jim Royer Monadic I/O in [1ex] Haskell 6 / 33

slide-7
SLIDE 7

Primitive I/O

putChar

()

getChar

Char Char

getChar :: IO Char putChar :: Char -> IO () getChar an action of type IO Char putChar ’x’ an action of type IO () what is ()? [Stage Directions: Open ghci in a window & play with these toys.]

Jim Royer Monadic I/O in [1ex] Haskell 7 / 33

slide-8
SLIDE 8

Combining actions, I

Problem We want to read a character and write it out again. So we want something like:

putChar

()

getChar

Char Since this is Haskell: when in need, introduce a new function.

Jim Royer Monadic I/O in [1ex] Haskell 8 / 33

slide-9
SLIDE 9

Combining actions, II

✭❃❃❂✮ ✿✿ ■❖ ❛ ✲❃ ✭❛ ✲❃ ■❖ ❜✮ ✲❃ ■❖ ❜ (built in)

  • -Sequentially compose two actions, passing any value
  • -produced by the first as an argument to the second.

Now we can define ❡❝❤♦ ✿✿ ■❖ ✭✮

echo = getChar >>= putChar

putChar

()

getChar

Char

[Stage Directions: In a terminal window, load io.hs into ghci.]

Jim Royer Monadic I/O in [1ex] Haskell 9 / 33

slide-10
SLIDE 10

Aside

✭❃❃❂✮ ✿✿ ■❖ ❛ ✲❃ ✭❛ ✲❃ ■❖ ❜✮ ✲❃ ■❖ ❜ (built in)

  • -Sequentially compose two actions, passing any value
  • -produced by the first as an argument to the second.

You can tell that the Haskell community thinks >>= is important since is their logo.

Jim Royer Monadic I/O in [1ex] Haskell 10 / 33

slide-11
SLIDE 11

Combining actions, III

Grab a character and print it twice

echoTwice :: IO () echoTwice = getChar >>= (\c -> putChar c >>= (\() -> putChar c))

As SPJ points out, the parens are optional. (Not that it helps readability much.) We drop the \() -> stuff via another combinator: ✭❃❃✮ ✿✿ ■❖ ❛ ✲❃ ■❖ ❜ ✲❃ ■❖ ❜ (built in)

m >> n = m >>= (\x -> n)

  • -n ignores m’s output.

Jim Royer Monadic I/O in [1ex] Haskell 11 / 33

slide-12
SLIDE 12

Combining actions, IV

So with ✭❃❃✮ ✿✿ ■❖ ❛ ✲❃ ■❖ ❜ ✲❃ ■❖ ❜ (built in)

m >> n = m >>= (\x -> n)

  • -n ignores m’s output.

We can rewrite echoTwice as: Grab a character and print it twice (revised)

echoTwice :: IO () echoTwice = getChar >>= \c -> putChar c >> putChar c

(Still rather clunky! But we aren’t done yet.)

Jim Royer Monadic I/O in [1ex] Haskell 12 / 33

slide-13
SLIDE 13

Combining actions, V

Next problem: Read two characters and return them

getTwoChars = getChar >>= \c1 -> getChar >>= \c2 -> ?? now what ??

Jim Royer Monadic I/O in [1ex] Haskell 13 / 33

slide-14
SLIDE 14

Combining actions, V

Next problem: Read two characters and return them

getTwoChars = getChar >>= \c1 -> getChar >>= \c2 -> ?? now what ??

Another combinator: return :: a -> IO a return

Jim Royer Monadic I/O in [1ex] Haskell 13 / 33

slide-15
SLIDE 15

Combining actions, V

Next problem: Read two characters and return them

getTwoChars = getChar >>= \c1 -> getChar >>= \c2 -> ?? now what ??

Another combinator: return :: a -> IO a return Read two characters and return them

getTwoChars = getChar >>= \c1 -> getChar >>= \c2 -> return (c1,c2)

Jim Royer Monadic I/O in [1ex] Haskell 13 / 33

slide-16
SLIDE 16

The do-notation

The clunky looking

getTwoChars = getChar >>= \c1 -> getChar >>= \c2 -> return (c1,c2)

Jim Royer Monadic I/O in [1ex] Haskell 14 / 33

slide-17
SLIDE 17

The do-notation

The clunky looking

getTwoChars = getChar >>= \c1 -> getChar >>= \c2 -> return (c1,c2)

can be rewritten as:

getTwoChars = do { c1 <- getChar; c2 <- getChar; return (c1,c2) }

Jim Royer Monadic I/O in [1ex] Haskell 14 / 33

slide-18
SLIDE 18

The do-notation

The clunky looking

getTwoChars = getChar >>= \c1 -> getChar >>= \c2 -> return (c1,c2)

can be rewritten as:

getTwoChars = do { c1 <- getChar; c2 <- getChar; return (c1,c2) }

and as

getTwoChars = do { c1 <- getChar ; c2 <- getChar ; return (c1,c2) }

Jim Royer Monadic I/O in [1ex] Haskell 14 / 33

slide-19
SLIDE 19

The do-notation

The clunky looking

getTwoChars = getChar >>= \c1 -> getChar >>= \c2 -> return (c1,c2)

can be rewritten as:

getTwoChars = do { c1 <- getChar; c2 <- getChar; return (c1,c2) }

and as

getTwoChars = do { c1 <- getChar ; c2 <- getChar ; return (c1,c2) }

as well as

getTwoChars = do c1 <- getChar c2 <- getChar return (c1,c2)

Jim Royer Monadic I/O in [1ex] Haskell 14 / 33

slide-20
SLIDE 20

The do-notation

The clunky looking

getTwoChars = getChar >>= \c1 -> getChar >>= \c2 -> return (c1,c2)

can be rewritten as:

getTwoChars = do { c1 <- getChar; c2 <- getChar; return (c1,c2) }

and as

getTwoChars = do { c1 <- getChar ; c2 <- getChar ; return (c1,c2) }

as well as

getTwoChars = do c1 <- getChar c2 <- getChar return (c1,c2)

Warning: <- is not an assignment operator!!!!

Jim Royer Monadic I/O in [1ex] Haskell 14 / 33

slide-21
SLIDE 21

The do-laws

The do-notation is syntactic sugar∗ do { x <- e; s } ≡ e >>= \x -> do { s } do { e; e } ≡ e >> do { s } do { e } ≡ e

∗ http://en.wikipedia.org/wiki/Syntactic_sugar

Jim Royer Monadic I/O in [1ex] Haskell 15 / 33

slide-22
SLIDE 22

Some examples, I

putStr :: String -> IO () (built in)

  • utputs a string

putStrLn :: String -> IO () (built in)

  • utputs a string followed by a new line

putStrLn str = do { putStr str; putStr "\n" } print :: Show a => a -> IO () (built in)

  • utputs a Haskell value

print x = putStrLn (show x) put4times :: String -> IO () print a string four times

put4times str = do putStrLn str putStrLn str putStrLn str putStrLn str

Jim Royer Monadic I/O in [1ex] Haskell 16 / 33

slide-23
SLIDE 23

Some examples, II

Print a string n times

putNtimes :: Int -> String -> IO () putNtimes n str = if n <= 1 then putStrLn str else do putStrLn str putNtimes (n-1) str

Gets a line of input

getLine :: IO String (built in) getLine = do c <- getChar if c == ’\n’ then return "" else do cs <- getLine return (c:cs)

Jim Royer Monadic I/O in [1ex] Haskell 17 / 33

slide-24
SLIDE 24

Aside

However, note that it is often easier to do the heavy lifting in the “functional” part of Haskell. E.g., in place of: Print a string n times

putNtimes :: Int -> String -> IO () putNtimes n str = if n <= 1 then putStrLn str else do putStrLn str putNtimes (n-1) str

instead you can do this: Print a string n times

putNtimes’ :: Int -> String -> IO () putNtimes’ n str = putStr $ unlines $ replicate n str

Jim Royer Monadic I/O in [1ex] Haskell 18 / 33

slide-25
SLIDE 25

Some examples, III

copy a line from input to output

copy :: IO () copy = do { line <- getLine ; putStrLn line }

read two lines, print them in reverse order and reversed

reverse2lines :: IO () reverse2lines = do line1 <- getLine line2 <- getLine putStrLn (reverse line2) putStrLn (reverse line1)

Convert a String to a Haskell value of type ❛

read :: Read a => String -> a (built in)

Read an Int from Input

getInt :: IO Int getInt = do { item <- getLine ; return (read item :: Int) }

Jim Royer Monadic I/O in [1ex] Haskell 19 / 33

slide-26
SLIDE 26

Some examples, IV

Problem Read a series of positive integers from input and sum them up. Stop reading when an integer ≤ 0 is found & then return the sum. A simple version

sumInts :: IO Int sumInts = do n <- getInt if n <= 0 then return 0 else do { m <- sumInts ; return (m+n) }

A chatty version

chattySum = do { putStrLn "Enter integers one per line" ; putStrLn "These will be summed until a 0 is entered." ; sum <- sumInts ; putStr "The sum is " ; print sum }

Jim Royer Monadic I/O in [1ex] Haskell 20 / 33

slide-27
SLIDE 27

Some examples, V

More built-ins

writeFile :: FilePath -> String -> IO () readFile :: FilePath -> IO String type FilePath = String

❝♦♣②❋✐❧❡ ✿✿ ❋✐❧❡P❛t❤ ✲❃ ❋✐❧❡P❛t❤ ✲❃ ■❖ ✭✮

copyFile source target = do s <- readFile source writeFile target s

s♦rt❋✐❧❡ ✿✿ ❋✐❧❡P❛t❤ ✲❃ ❋✐❧❡P❛t❤ ✲❃ ■❖ ✭✮

sortFile source target = do s <- readFile source writeFile target (sortLines s) sortLines = unlines . sort . lines

Jim Royer Monadic I/O in [1ex] Haskell 21 / 33

slide-28
SLIDE 28

Haskell keeps pure and impure functions apart

Pure ≡ no side-effects (easy to debug, get correct) In the above examples, IO types marks a function as impure. Roughly, you can only get access to an “outside” value inside of a do-block — where you may apply pure functions to it. Keep IO actions simple Do most of the serious work via pure functions. Example: The sortFile example from before.

Jim Royer Monadic I/O in [1ex] Haskell 22 / 33

slide-29
SLIDE 29

Things that FAIL to work in a do-block, I

This will not compile.

attempt1 :: IO Int attempt1 = do { x <- 12 ; return x } Problem In the context of this code, in: x <- ?? the type of ?? must be (IO Int).

However, this works.

attempt2 :: IO Int attempt2 = do { let x = 12 ; return x } Do-blocks allow lets Use a let to introduce local variables.

Jim Royer Monadic I/O in [1ex] Haskell 23 / 33

slide-30
SLIDE 30

The do-laws, revised

do { x <- e; s } ≡ e >>= \x -> do { s } do { let x = e; s } ≡ let x = e in do { s } do { e; e } ≡ e >> do { s } do { e } ≡ e Fact: When you are typing to the ghci prompt, you are in an (IO ()) do-block.

Jim Royer Monadic I/O in [1ex] Haskell 24 / 33

slide-31
SLIDE 31

Things that FAIL to work in a do-block, II

This will not compile.

attempt3 = do { return 12 } Problem Haskell does not have enough information to figure out the type of the result.

However, this works.

attempt4 :: IO Int attempt4 = do { return 12 } Also try: attempt5 :: Maybe Int attempt5 = do { return 12 }

Jim Royer Monadic I/O in [1ex] Haskell 25 / 33

slide-32
SLIDE 32

More examples: Module IOTools, I

Safer versions of getChar & putChar

getc :: IO (Maybe Char) getc = do eof <- isEOF if eof then return Nothing else do c <- getChar return (Just c) putc :: Char -> IO () putc = putChar

Copy std-in to std-out

copy :: IO () copy = do ch <- getc case ch of Nothing -> return () Just c

  • > do putc c

copy

Jim Royer Monadic I/O in [1ex] Haskell 26 / 33

slide-33
SLIDE 33

More examples: Module IOTools, II

Count the number of characters in the input

charcount :: IO () charcount = do nc <- cc 0 putStrLn (show nc) where cc nc = do ch <- getc case ch of Nothing -> return nc Just c

  • > cc $! nc + 1

($!) :: (a -> b) -> a -> b Strict (call-by-value) application operator

Jim Royer Monadic I/O in [1ex] Haskell 27 / 33

slide-34
SLIDE 34

More examples: Module IOTools, III

Count the number of lines in the input

linecount :: IO () linecount = do { nl <- lc 0 ; putStrLn (show nl) } where lc nl = do ch <- getc case ch of Nothing

  • > return nl

Just ’\n’ -> lc $! nl + 1 Just _

  • > lc nl

Jim Royer Monadic I/O in [1ex] Haskell 28 / 33

slide-35
SLIDE 35

More examples: Module IOTools, IV

interact :: (String -> String) -> IO ()

Standard input is passed to the (String -> String)-function as its argument; the resulting string is output to standard output.

Alternatives

showLn v = (show v)++"\n" copy’ = interact id charcount’ = interact (showLn . length) linecount’ = interact (showLn . length . lines)

Often, using interact results in faster code that the imperative-style programs.

Jim Royer Monadic I/O in [1ex] Haskell 29 / 33

slide-36
SLIDE 36

Control structures, I

An IO-action is just another value to be passed around. So we can build our own control structures. repeat a particular IO-action forever

forever :: IO () -> IO () forever a = do { a ; forever a }

repeat a particular IO-action n times

repeatN :: Int -> IO () -> IO () repeatN 0 a = return () repeatN n a = do { a ; repeatN (n-1) a }

Do an IO action of each element of a list

for :: [a] -> (a -> IO b) -> IO () for [] fa = return () for (x:xs) fa = do { fa x ; for xs fa }

Jim Royer Monadic I/O in [1ex] Haskell 30 / 33

slide-37
SLIDE 37

Control structures, II

Do an IO action of each element of a list

for :: [a] -> (a -> IO b) -> IO () for [] fa = return () for (x:xs) fa = do { fa x ; for xs fa }

Alternative definition

for xs fa = sequence_ [fa x | x <- xs] where sequence_ :: [IO a] -> IO () (built in) sequence_ as = foldr (>>) (return ()) as

  • r if you perfer

sequence_ [] = return () sequence_ (a:as) = a >> (sequence_ as)

Jim Royer Monadic I/O in [1ex] Haskell 31 / 33

slide-38
SLIDE 38

Control structures, III

Do a list of IO actions and return the list of results

sequence :: [IO a] -> IO [a] (built in) sequence [] = return [] sequence (a:as) = do r <- a rs <- sequence as return (r:rs) main = do a <- getLine b <- getLine c <- getLine print [a,b,c] main = do rs <- sequence [getLine,getLine,getLine] print rs

. . . and so on.

Jim Royer Monadic I/O in [1ex] Haskell 32 / 33

slide-39
SLIDE 39

Operational semantics of IO (too briefly)

Labeled Transitions P ! c → Q P can move to Q by writing charac- ter c to standard output P ? c → Q P can move to Q by reading the character c from standard output First Two Transition Rules { putChar c } ! c → { return () } { getChar } ? c → { return c }

Jim Royer Monadic I/O in [1ex] Haskell 33 / 33