CS 541 Lecture -*- Outline -*- * Input/Output in functional languages references: Hudak's computing surveys paper (ACM CS, Vol 21, Num 3, Sept. 1989, pages 359-411) Wadler's comupting surevys paper (ACM CS, Vol 29, Num 2, Sept. 1997, pages 240-263) ** the problem ------------------------------------------ THE PROBLEM WITH I/O WANT: referential transparency BUT: Input/Output seems to be a def: a *reactive system* is a system that Examples: ------------------------------------------ ... side effect ... can interact with its environment. Typically it does something like: prompt, read, prompt, read, compute, output, read,... ... editors, web browsers, robots, bank ATM machines, airplanes, ... ------------------------------------------ NONDETERMINISM vs. REFERENTIAL TRANSPARENCY Suppose the expression choose e1 e2 returns either e1 or e2, at random. Does that make a language not referentially transparent? Operating system example. [ merge ] __> [ O. S. ] ___> [ split ] ^ ^ | | | |_______ [ prog 1 ] <_______| | | | | . | | . | | . | |____________ [ prog n ] <__________| ------------------------------------------ race conditions may lead to nondeterminism e.g., reading input from two terminals... => loss of referential transparency? e.g., programs that communicate with each other while running in parallel how does a program receive a list of responses before it has generated any requests? ** solutions *** lazy synchronized streams ------------------------------------------ LAZY SYNCHRONIZED STREAMS Program has type [Response] -> [Request] Examples: data Request = Getq | Putq Char data Response = Getp Char | Putp echo :: [Response] -> [Request] echo p = Getq : (case p of (Getp c : p2) -> if (c == '\n') then [] else Putq c : (case p2 of (Putp : p3) -> echo p3 ------------------------------------------ synchronous stream model request: read or write a file response: contents of file, acks, errors the synchronization happens when response is generated before the next request is issued; this relies crucially on lazy evaluation. problems with this: have to make sure that request is issued before the next response is consumed. modularity (hard to compose) *** continuations ------------------------------------------ CONTINUATIONS Program has type Answer All parts of the program that do I/O take an Answer continuation representing the rest of the program Examples: putcK :: Char -> Answer -> Answer getcK :: (Char -> Answer) -> Answer putcK c k -- getcK k -- echo :: Answer -> Answer echo k = getcK (\c -> if (c == '\n') then k else putcK c ( echo k)) ------------------------------------------ You can think of Answer as the same as [Response] -> [Request], although it doesn't have to be that. ... puts c on the output, and then does k ... gets a char c, then applies k to c can compose this style of programs, so better than streams problems: passing the continuation around is annoying *** monads ------------------------------------------ MONADIC I/O Program has type IO () Examples putc :: Char -> IO () getc :: IO Char echo :: IO() echo = getc >>= \c -> if (c == '\n') then return () else putc c >> echo ------------------------------------------ Compare this to the continuation version. Q: What are the similarities to continuations? advantages? can code the IO monad using continuations type IO a = (a -> Answer) -> Answer *** other approaches linear logic - types ensure that state is always single threaded, so don't need to make copies of memory advantages: more flexible if you want to break up state into non-interacting chunks problems: requires a more sophisticated type system, has more clutter See Wadler's paper for more details