The IO Monad Mooly Sagiv Slides from John Mitchell Kathleen Fisher Simon Peyton Jones Reading: “Tackling the Awkward Squad” “Real World Haskell,” Chapter.

Slides:



Advertisements
Similar presentations
Kathleen Fisher cs242 Reading: Tackling the Awkward Squad, Sections 1-2Tackling the Awkward Squad Real World Haskell, Chapter 7: I/OReal World Haskell.
Advertisements

Introduction to Compilation of Functional Languages Wanhe Zhang Computing and Software Department McMaster University 16 th, March, 2004.
Kathleen Fisher cs242 Reading: “A history of Haskell: Being lazy with class”,A history of Haskell: Being lazy with class Section 6.4 and Section 7 “Monads.
8. Introduction to Denotational Semantics. © O. Nierstrasz PS — Denotational Semantics 8.2 Roadmap Overview:  Syntax and Semantics  Semantics of Expressions.
1 Programming Languages (CS 550) Lecture Summary Functional Programming and Operational Semantics for Scheme Jeremy R. Johnson.
Introduction to Programming Languages Nai-Wei Lin Department of Computer Science and Information Engineering National Chung Cheng University.
Chapter 16 Communicating With the Outside World. Motivation  In Chapter 3 we learned how to do basic IO, but it was fairly limited.  In this chapter.
Functional Programming Universitatea Politehnica Bucuresti Adina Magda Florea
MATHEMATICAL FOUNDATIONS SUPPLEMENTAL MATERIAL – NOT ON EXAM.
Kathleen Fisher cs242 Reading: “Tackling the Awkward Squad,” Sections 1-2Tackling the Awkward Squad “Real World Haskell,” Chapter 7: I/OReal World Haskell.
Grab Bag of Interesting Stuff. Topics Higher kinded types Files and handles IOError Arrays.
Chapter 3 Simple Graphics. Side Effects and Haskell  All the programs we have seen so far have no “side-effects.” That is, programs are executed only.
Slides prepared by Rose Williams, Binghamton University Chapter 1 Getting Started 1.1 Introduction to Java.
A Lightning Tour of Haskell Lecture 1, Designing and Using Combinators John Hughes.
Advanced Programming Handout 12 Higher-Order Types (SOE Chapter 18)
Semantics with Applications Mooly Sagiv Schrirber html:// Textbooks:Winskel The.
Type Inference: CIS Seminar, 11/3/2009 Type inference: Inside the Type Checker. A presentation by: Daniel Tuck.
CS510AP Quick Check Monads. QuickCheck Quick check is a Haskell library for doing random testing. You can read more about quickcheck at –
Kathleen Fisher Tufts University Thanks to Simon Peyton Jones for many of these slides. References: “Real World Haskell”, Chapter 0 & 7 (
C++ fundamentals.
0 PROGRAMMING IN HASKELL Chapter 9 - Interactive Programs.
Introduction to Java Appendix A. Appendix A: Introduction to Java2 Chapter Objectives To understand the essentials of object-oriented programming in Java.
High-Level Programming Languages: C++
Lists in Python.
Coupling and Cohesion Pfleeger, S., Software Engineering Theory and Practice. Prentice Hall, 2001.
Haskell Chapter 8. Input and Output  What are I/O actions?  How do I/O actions enable us to do I/O?  When are I/O actions actually performed?
1 Names, Scopes and Bindings Aaron Bloomfield CS 415 Fall
Comp150PLD Reading: “Tackling the Awkward Squad,” Sections 1-2Tackling the Awkward Squad “Real World Haskell,” Chapter 7: I/OReal World Haskell Thanks.
CSC 580 – Theory of Programming Languages, Spring, 2009 Week 9: Functional Languages ML and Haskell, Dr. Dale E. Parson.
Chapter 7 File I/O 1. File, Record & Field 2 The file is just a chunk of disk space set aside for data and given a name. The computer has no idea what.
Cohesion and Coupling CS 4311
CE Operating Systems Lecture 3 Overview of OS functions and structure.
Looping and Counting Lecture 3 Hartmut Kaiser
CMP-MX21: Lecture 4 Selections Steve Hordley. Overview 1. The if-else selection in JAVA 2. More useful JAVA operators 4. Other selection constructs in.
© M. Winter COSC 4P41 – Functional Programming Programming with actions Why is I/O an issue? I/O is a kind of side-effect. Example: Suppose there.
TIVDM2Functional Programming Language Concepts 1 Concepts from Functional Programming Languages Peter Gorm Larsen.
0 Odds and Ends in Haskell: Folding, I/O, and Functors Adapted from material by Miran Lipovaca.
Arvind Computer Science and Artificial Intelligence Laboratory M.I.T. L13-1 October 26, 2006http:// Monadic Programming October.
Fall 2002CS 150: Intro. to Computing1 Streams and File I/O (That is, Input/Output) OR How you read data from files and write data to files.
1-1 An Introduction to Functional Programming Sept
Lee CSCE 314 TAMU 1 CSCE 314 Programming Languages Interactive Programs: I/O and Monads Dr. Hyunyoung Lee.
CMSC 330: Organization of Programming Languages Operational Semantics a.k.a. “WTF is Project 4, Part 3?”
CMSC 330: Organization of Programming Languages Operational Semantics.
Operational Semantics Mooly Sagiv Tel Aviv University Sunday Scrieber 8 Monday Schrieber.
Operational Semantics Mooly Sagiv Reference: Semantics with Applications Chapter 2 H. Nielson and F. Nielson
Operational Semantics Mooly Sagiv Reference: Semantics with Applications Chapter 2 H. Nielson and F. Nielson
Coupling and Cohesion Pfleeger, S., Software Engineering Theory and Practice. Prentice Hall, 2001.
24-Jun-16 Haskell Dealing with impurity. Purity Haskell is a “pure” functional programming language Functions have no side effects Input/output is a side.
Functional Programming
Haskell Chapter 8.
the IO Monad Kathleen Fisher cs242
Types CSCE 314 Spring 2016.
Learning to Program D is for Digital.
Theory of Computation Lecture 4: Programs and Computable Functions II
PROGRAMMING IN HASKELL
6.001 SICP Variations on a Scheme
CS 326 Programming Languages, Concepts and Implementation
CSEP505: Programming Languages Lecture 8: Haskell, Laziness, IO Monad
User-Defined Functions
I/O in C Lecture 6 Winter Quarter Engineering H192 Winter 2005
Haskell Dealing with impurity 30-Nov-18.
Haskell Dealing with impurity 30-Nov-18.
Haskell Dealing with impurity 8-Apr-19.
PROGRAMMING IN HASKELL
Grab Bag of Interesting Stuff
Programming Languages
Theory of Computation Lecture 4: Programs and Computable Functions II
Haskell Dealing with impurity 29-May-19.
Haskell Dealing with impurity 28-Jun-19.
Advanced Functional Programming
Presentation transcript:

The IO Monad Mooly Sagiv Slides from John Mitchell Kathleen Fisher Simon Peyton Jones Reading: “Tackling the Awkward Squad” “Real World Haskell,” Chapter 7: I/O

Beauty... Functional programming is beautiful: – Concise and powerful abstractions higher-order functions, algebraic data types, parametric polymorphism, principled overloading,... – Close correspondence with mathematics Semantics of a code function is the mathematical function Equational reasoning: if x = y, then f x = f y Independence of order-of-evaluation (Confluence, aka Church-Rosser) e1 * e2 e1’ * e2e1 * e2’ result The compiler can choose the best sequential or parallel evaluation order

...and the Beast But to be useful as well as beautiful, a language must manage the “Awkward Squad”: – Input/Output – Imperative update – Error recovery (eg, timeout, divide by zero, etc.) – Foreign-language interfaces – Concurrency control The whole point of a running a program is to interact with the external environment and affect it "Don't let the awkward squad fire over me Robert Bruns

The Direct Approach Just add imperative constructs “the usual way” – I/O via “functions” with side effects: – Imperative operations via assignable reference cells: – Error recovery via exceptions – Foreign language procedures mapped to “functions” – Concurrency via operating system threads Can work if language determines evaluation order – Ocaml, Standard ML are good examples of this approach putchar ‘x’ + putchar ‘y’ z = ref 0; z := !z + 1; f(z); w = !z (* What is the value of w? *)

But what if we are “lazy”? Example: – Output depends upon the evaluation order of ( + ) Example: – Output depends on how list is used – If only used in length ls, nothing will be printed because length does not evaluate elements of list In a lazy functional language, like Haskell, the order of evaluation is deliberately undefined, so the “direct approach” will not work res = putchar ‘x’ + putchar ‘y’ ls = [putchar ‘x’, putchar ‘y’]

Fundamental question Is it possible to regard pure Haskell as the basic programming paradigm, and add imperative features without changing the meaning of pure Haskell expressions?

Tackling the Awkward Squad Basic conflict – Laziness and side effects are incompatible Historical aside: “Jensen’s device” in Algol 60; see book (p96) – Side effects are important! History – This conflict was embarrassing to the lazy functional programming community – In early 90’s, a surprising solution (the monad) emerged from an unlikely source (category theory). Haskell IO monad tackles the awkward squad – I/O, imperative state, exceptions, foreign functions, concurrency – Practical application of theoretical insight by E Moggi

Web Server Example The reading uses a web server as an example Lots of I/O, need for error recovery, need to call external libraries, need for concurrency Web server Client 1Client 2Client 3Client lines of Haskell 700 connections/sec Writing High-Performance Server Applications in Haskell, by Simon Marlow

Monadic Input and Output

Problem A functional program defines a pure function, with no side effects The whole point of running a program is to have some side effect The term “side effect” itself is misleading

Before Monads Streams – Program sends stream of requests to OS, receives stream of responses Continuations – User supplies continuations to I/O routines to specify how to process results (will cover continuations Wed) World-Passing – The “State of the World” is passed around and updated, like other data structures – Not a serious contender because designers didn’t know how to guarantee single-threaded access to the world Haskell 1.0 Report adopted Stream model – Stream and Continuation models were discovered to be inter-definable

Stream Model: Basic Idea Move side effects outside of functional program Haskell main :: String -> String Gets more complicated … – But what if you need to read more than one file? Or delete files? Or communicate over a socket?... Haskell main program standard input location (file or stdin) standard output location (file or stdout) Wrapper Program, written in some other language

Stream Model Move side effects outside of functional program If Haskell main :: [Response] -> [Request] Laziness allows program to generate requests prior to processing any responses Haskell program [ Response ][ Request ]

Stream Model Enrich argument and return type of main to include all input and output events Wrapper program interprets requests and adds responses to input main :: [Response] -> [Request] data Request = ReadFile Filename | WriteFile FileName String | … data Response= RequestFailed | ReadOK String | WriteOk | Success | …

Example in Stream Model Haskell 1.0 program asks user for filename, echoes name, reads file, and prints to standard out The ~ denotes a lazy pattern, which is evaluated only when the corresponding identifier is needed main :: [Response] -> [Request] main ~(Success : ~((Str userInput) : ~(Success : ~(r4 : _)))) = [ AppendChan stdout "enter filename\n", ReadChan stdin, AppendChan stdout name, ReadFile name, AppendChan stdout (case r4 of Str contents -> contents Failure ioerr -> "can’t open file") ] where (name : _) = lines userInput

Stream Model is Awkward! Hard to extend – New I/O operations require adding new constructors to Request and Response types, modifying wrapper Does not associate Request with Response – easy to get “out-of-step,” which can lead to deadlock Not composable – no easy way to combine two “main” programs... and other problems!!!

Monadic I/O: The Key Idea A value of type (IO t) is an “action” When performed, an action may do some input/output and deliver a result of type t

Eugenio Moggi

Monads General concept from category theory – Adopted in Haskell for I/O, side effects, … A monad consists of: – A type constructor M – A function bind :: M a -> ( a -> M b) -> M b – A function return :: a -> M a Plus: – Laws about how these operations interact

A Helpful Picture A value of type (IO t) is an “action.” When performed, it may do some input/output before delivering a result of type t type IO t = World -> (t, World) IO t result :: t

Actions are First Class “Actions” are sometimes called “computations” An action is a first-class value Evaluating an action has no effect; performing the action has the effect A value of type (IO t) is an “action.” When performed, it may do some input/output before delivering a result of type t type IO t = World -> (t, World)

Simple I/O getChar Char putChar () Char getChar :: IO Char putChar :: Char -> IO () main :: IO () main = putChar ‘x’ Main program is an action of type IO ()

Connection Actions To read a character and then write it back out, we need to connect two actions putChar () getChar Char The “bind” combinator lets us make these connections

The Bind Combinator (>>=) We have connected two actions to make a new, bigger action putChar () Char getChar (>>=) :: IO a -> (a -> IO b) -> IO b echo :: IO () echo = getChar >>= putChar

The (>>=) Combinator Operator is called bind because it binds the result of the left-hand action in the action on the right Performing compound action a >>= \x->b : – performs action a, to yield value r – applies function \x->b to r – performs the resulting action b{x <- r} – returns the resulting value v b v a x r

Printing a Character Twice The parentheses are optional because lambda abstractions extend “as far to the right as possible” The putChar function returns unit, so there is no interesting value to pass on echoDup :: IO () echoDup = getChar >>= (\c -> putChar c >>= (\() -> putChar c ))

The (>>) Combinator The “then” combinator (>>) does sequencing when there is no value to pass: (>>) :: IO a -> IO b -> IO b m >> n = m >>= (\_ -> n) echoDup :: IO () echoDup = getChar >>= \c -> putChar c >> putChar c echoTwice :: IO () echoTwice = echo >> echo

Getting Two Characters We want to return (c1,c2). – But, (c1,c2) :: (Char, Char) – We need to return value of type IO(Char, Char) We need to have some way to convert values of “plain” type into the I/O Monad getTwoChars :: IO (Char,Char) getTwoChars = getChar>>= \c1 -> getChar>>= \c2 -> ????

The return Combinator The action (return v) does no IO and immediately returns v: return :: a -> IO a return getTwoChars :: IO (Char,Char) getTwoChars = getChar>>= \c1 -> getChar>>= \c2 -> return (c1,c2)

Main IO The main program is a single big IO operation main :: IO () main= getLine >>= \cs -> putLine (reverse cs)

The “do” Notation The “do” notation adds syntactic sugar to make monadic code easier to read Do syntax designed to look imperative -- Do Notation getTwoCharsDo :: IO(Char,Char) getTwoCharsDo = do { c1 <- getChar ; c2 <- getChar ; return (c1,c2) } -- Plain Syntax getTwoChars :: IO (Char,Char) getTwoChars = getChar>>= \c1 -> getChar>>= \c2 -> return (c1,c2)

Desugaring “do” Notation The “do” notation only adds syntactic sugar: do { x >= \x -> do { es } do { e; es }=e >> do { es } do { e } =e do {let ds; es} = let ds in do {es} The scope of variables bound in a generator is the rest of the “do” expression The last item in a “do” expression must be an expression

Syntactic Variations The following are equivalent: do { x1 <- p1;...; xn <- pn; q } do x1 <- p1... xn <- pn q do x1 <- p1;...; xn <- pn; q If semicolons are omitted, then the generators must align. Indentation replaces punctuation.

Bigger Example The getLine function reads a line of input: getLine :: IO [Char] getLine = do { c <- getChar ; if c == '\n' then return [] else do { cs <- getLine; return (c:cs) }} Note the “regular” code mixed with the monadic operations and the nested “do” expression

An Analogy: Monad as Assembly Line Each action in the IO monad is a stage in an assembly line For an action with type IO a, the type – tags the action as suitable for the IO assembly line via the IO type constructor – indicates that the kind of thing being passed to the next stage in the assembly line has type a The bind operator “snaps” two stages together to build a compound stage The return operator converts a pure value into a stage in the assembly line The assembly line does nothing until it is turned on The only safe way to “run” an IO assembly is to execute the program, either using ghci or running an executable 12

Running the program turns on the IO assembly line The assembly line gets “the world” as its input and delivers a result and a modified world The types guarantee that the world flows in a single thread through the assembly line Powering the Assembly Line Result ghci or compiled program

Monad Laws return x >>= f = f x m >>= return = m do { x <- m1; y <- m2; m3 } do { y <- do { x <- m1; m2 } m3} = x not in free vars of m3 m1 >>= ( x. m2 >>= y. m3)) = (m1 >>= ( x. m2)) >>= y. m3)

Derived Laws for (>>) and done done >> m = m m >> done = m m1 >> (m2 >> m3) = (m1 >> m2) >> m3 (>>) :: IO a -> IO b -> IO b m >> n = m >>= (\_ -> n) done :: IO () done = return ()

Reasoning Using the monad laws and equational reasoning, we can prove program properties putStr :: String -> IO () putStr [] = done putStr (c:s) = putChar c >> putStr s Proposition: putStr r >> putStr s = putStr (r ++ s)

putStr :: String -> IO () putStr [] = done putStr (c:cs) = putChar c >> putStr cs Proof: By induction on r. Base case: r is [] putStr [] >> putStr s = ( definition of putStr ) done >> putStr s = ( first monad law for >>) putStr s = (definition of ++) putStr ([] ++ s) Induction case: r is (c:cs) … Proposition : putStr r >> putStr s = putStr (r ++ s)

Control Structures Values of type ( IO t ) are first class, so we can define our own control structures Example use: forever :: IO () -> IO () forever a = a >> forever a repeatN :: Int -> IO () -> IO () repeatN 0 a = return () repeatN n a = a >> repeatN (n-1) a Main> repeatN 5 (putChar 'h')

For Loops Values of type ( IO t ) are first class, so we can define our own control structures Example use: for :: [a] -> (a -> IO b) -> IO () for [] fa = return () for (x:xs) fa = fa x >> for xs fa Main> for [1..10] (\x -> putStr (show x))

Sequencing Example use: sequence :: [IO a] -> IO [a] sequence [] = return [] sequence (a:as) = do { r <- a; rs <- sequence as; return (r:rs) } Main> sequence [getChar, getChar, getChar] A list of IO actions An IO action returning a list

First Class Actions Slogan: First-class actions let programmers write application-specific control structures

IO Provides Access to Files The IO Monad provides a large collection of operations for interacting with the “World” For example, it provides a direct analogy to the Standard C library functions for files: openFile :: FilePath -> IOMode -> IO Handle hPutStr :: Handle -> String -> IO () hGetLine :: Handle -> IO String hClose :: Handle -> IO ()

References The IO operations let us write programs that do I/O in a strictly sequential, imperative fashion Idea: We can leverage the sequential nature of the IO monad to do other imperative things! A value of type IORef a is a reference to a mutable cell holding a value of type a data IORef a -- Abstract type newIORef :: a -> IO (IORef a) readIORef :: IORef a -> IO a writeIORef :: IORef a -> a -> IO ()

Example Using References But this is terrible! Contrast with: sum [1..n]. Claims to need side effects, but doesn’t really import Data.IORef -- import reference functions -- Compute the sum of the first n integers count :: Int -> IO Int count n = do { r <- newIORef 0; addToN r 1 } where addToN :: IORef Int -> Int -> IO Int addToN r i | i > n = readIORef r | otherwise = do { v <- readIORef r ; writeIORef r (v + i) ; addToN r (i+1)}

Example Using References import Data.IORef -- import reference functions -- Compute the sum of the first n integers count :: Int -> IO Int count n = do { r <- newIORef 0; addToN r 1 } where addToN :: IORef Int -> Int -> IO Int addToN r i | i > n = readIORef r | otherwise = do { v <- readIORef r ; writeIORef r (v + i) ; addToN r (i+1)} Just because you can write C code in Haskell, doesn’t mean you should!

A Second Example Track the number of chars written to a file Here it makes sense to use a reference type HandleC = (Handle, IORef Int) openFileC :: FilePath -> IOMode -> IO HandleC openFileC file mode = do { h <- openFile file mode ; v <- newIORef 0 ; return (h,v) } hPutStrC :: HandleC -> String -> IO() hPutStrC (h,r) cs = do { v <- readIORef r ; writeIORef r (v + length cs) ; hPutStr h cs }

The IO Monad as ADT All operations return an IO action, but only bind (>>=) takes one as an argument Bind is the only operation that combines IO actions, which forces sequentiality Within the program, there is no way out! return :: a -> IO a (>>=) :: IO a -> (a -> IO b) -> IO b getChar :: IO Char putChar :: Char -> IO ()... more operations on characters... openFile :: [Char] -> IOMode -> IO Handle... more operations on files... newIORef :: a -> IO (IORef a)... more operations on references...

Irksome Restriction? Suppose you wanted to read a configuration file at the beginning of your program: The problem is that readFile returns an IO String, not a String Option 1: Write entire program in IO monad But then we lose the simplicity of pure code Option 2: Escape from the IO Monad using a function from IO String -> String But this is the very thing that is disallowed! configFileContents :: [String] configFileContents = lines (readFile "config") -- WRONG! useOptimisation :: Bool useOptimisation = "optimise" ‘elem‘ configFileContents

Type-Unsafe Haskell Programming Reading a file is an I/O action, so in general it matters when we read the file But we know the configuration file will not change during the program, so it doesn’t matter when we read it This situation arises sufficiently often that Haskell implementations offer one last unsafe I/O primitive: unsafePerformIO unsafePerformIO :: IO a -> a configFileContents :: [String] configFileContents = lines(unsafePerformIO(readFile "config"))

unsafePerformIO The operator has a deliberately long name to discourage its use Its use comes with a proof obligation: a promise to the compiler that the timing of this operation relative to all other operations doesn’t matter Breaks type safety unsafePerformIO :: IO a -> a Result act Invent World Discard World

Implementation GHC uses “world-passing semantics” for the IO monad It represents the “world” by an un-forgeable token of type World, and implements bind and return as: Using this form, the compiler can do its normal optimizations. The dependence on the world ensures the resulting code will still be single-threaded The code generator then converts the code to modify the world “in-place.” type IO t = World -> (t, World) return :: a -> IO a return a = \w -> (a,w) (>>=) :: IO a -> (a -> IO b) -> IO b (>>=) m k = \w -> case m w of (r,w’) -> k r w’

Monads What makes the IO Monad a Monad? A monad consists of: – A type constructor M – A function bind :: M a -> ( a -> M b) -> M b – A function return :: a -> M a Plus: Laws about how these interact

A Denotational Semantics? type IO a = World -> (a, World) loop:: IO() loop = loop  loop  =? loopX:: IO() loopX = putchar ‘x’ >>= loopX  loopX  = Can be defined with traces Another alternative is an operational semantics

Summary A complete Haskell program is a single IO action called main. Inside IO, code is single-threaded Big IO actions are built by gluing together smaller ones with bind (>>=) and by converting pure code into actions with return IO actions are first-class – They can be passed to functions, returned from functions, and stored in data structures – So it is easy to define new “glue” combinators The IO Monad allows Haskell to be pure while efficiently supporting side effects The type system separates the pure from the effectful code

Comparison In languages like ML or Java, the fact that the language is in the IO monad is baked in to the language. There is no need to mark anything in the type system because it is everywhere. In Haskell, the programmer can choose when to live in the IO monad and when to live in the realm of pure functional programming So it is not Haskell that lacks imperative features, but rather the other languages that lack the ability to have a statically distinguishable pure subset