CSE 505 Lecture Notes - Oct 5 I/O in functional languages Option 1: Forget it. Don't even think about it. (cf Algol-60) Option 2: Streams. Option 3: Continuations. streams - used for I/O in many operating systems (e.g. Unix) and imperative languages (e.g. Smalltalk) read streams: get a character; also things like read a number, get a line of text, etc. write streams: write a character; etc. In a functional language we can treat a file to be read (or the standard input stream) as a list of characters. Using lazy evaluation, we don't need to have the entire input available before program execution starts. We could extend this to a windowing environment to have a list of events (mouse movements, button pushes, keystrokes). Miranda: read "myfile" $- || standard in Output A functional program could produce a list of characters, lazily consumed by the operating system. In Miranda the value of a command level expression is a list of `system messages' sys_message ::= Stdout [char] | Stderr [char] | Tofile [char] [char] | Closefile [char] | Appendfile [char] | System [char] | Exit num The system `prints' such a list of messages by reading it in order from left to right, evaluating and obeying each message in turn as it is encountered. The effect of the various messages is as follows. Stdout string The list of characters `string' is transmitted to the standard output, which will normally be connected to the user's screen. So for example the effect of obeying [Stdout "!!!"] is that three exclamation marks appear on the screen. System string Causes `string' to be executed as a shell command (by `/bin/sh') at this point in time. [Stdout "hi there"] ++ [System "rm *"] ++ [Stdout "I just removed all your files "] Potentially very non-functional ... [System "date"] is not referentially transparent, for example; can write and then read a file; etc. wrap x = [Stdout x] This requires that x be a string - if not, the operator "show" is applied to x to convert it into a printable representation. This explains how the Miranda system is able to function in its standard `desk-calculator' mode. When you type, say `2+3', the compiler notices that this is not of type [sys_message] and silently converts it to [Stdout (show(2+3))] before obeying it. ------------------------------ Reading and writing files is easy - can use Miranda for writing little Unix programs Interative I/O ... possible but error-prone due to lazy evaluation ---------- || example of interactive Miranda program -- get a series of pairs of || numbers and add them || lines takes a list of characters, and returns a list of lists of || characters (breaking on newlines) calc = calcnums (lines $-) calcnums ns = [Stdout "first number: "] ++ (seq a []) ++ [Stdout "second number: "] ++ (seq b []) ++ [Stdout "sum: "] ++ [Stdout (show (numval a + numval b) ++ "\n")] ++ calcnums rest where a = hd ns b = hd (tl ns) rest = tl (tl ns) -------------------- || like calc.m, except check correctly for ^d calc = calcnums (lines $-) calcnums as = [Stdout "first number: "] ++ calc2 as calc2 [] = [Stdout "\n\n^d in input -- bye from calculator"] calc2 (b:bs) = calc3 (numval b) bs calc3 a ns = [Stdout "second number: "] ++ (seq b []) ++ [Stdout "sum: "] ++ [Stdout (show (a+b) ++ "\n")] ++ calcnums (tl ns) where b = numval (hd ns) -------------------- Ugh! Streams also available in Haskell Other technique used in Haskell: continuations In continuation-style programming, rather than returning a result, you call another function with the result. Very general: almost like the functional version of a goto. example: factorial n result_func = aux_factorial n result_func 1 aux_factorial n result_func prod = result_func prod, if n=0 aux_factorial (n-1) result_func (prod*n), if n>0 ---------- as above, but with a second error function factorial n f error_func = aux n f 1 error_func aux n f prod err = f prod, if n=0 aux (n-1) f (prod*n) error_func, if n>0 err "negative argument to factorial", otherwise ---------------------------------------- Haskell example of continuation-based I/O (from the Gentle Introduction) main = writeFile "ReadMe" s1 exit ( readfile "ReadMe" exit (\s2-> appendChan stdout (if s1==s2 then "contents match" else "something intervened!") exit done)) writeFile :: Name -> String -> FailCont -> SuccCont -> Dialog readFile :: Name -> String -> FailCont -> StrCont -> Dialog appendChan :: Name -> String -> FailCont -> SuccCont -> Dialog done :: Dialog exit :: FailCont (FailCont is the type failure continuation)