470 likes | 592 Views
Functional Programming. Universitatea Politehnica Bucuresti 2008-2009 Adina Magda Florea http://turing.cs.pub.ro/fp_09. Lecture No. 10 & 11. Type declarations Data declarations Recursive types Arithmetic expressions Binary trees Type inference I/O. Note. The slides are from:
E N D
Functional Programming Universitatea Politehnica Bucuresti2008-2009 Adina Magda Florea http://turing.cs.pub.ro/fp_09
Lecture No. 10 & 11 • Type declarations • Data declarations • Recursive types • Arithmetic expressions • Binary trees • Type inference • I/O
Note The slides are from: Programming in Haskell, Graham Hutton, Cambridge University Press (January 15, 2007)
Type inference • Type signatures are not mandatory. • Haskell has a type inference system • Type inference in Haskell is decidable isL c = c == 'l' • This function takes a character and sees if it is an 'l' character.
Type inference isL c = c == 'l' • The compiler derives the type for isL something like the following (==) :: a -> a -> Bool 'l' :: Char • Replacing the second ''a'' in the signature for (==) with the type of 'l': (==) :: Char -> Char -> Bool isL :: Char -> Bool • the return value from the call to (==) becomes the return value of isL function.
Type inference • isL is a function which takes a single argument. • We discovered that this argument must be of type Char. • Finally, we derived that we return a Bool. • So, we can confidently say that isL has the type: isL :: Char -> Bool isL c = c == 'l'
Reasons to use type signatures • Documentation: the most prominent reason is that it makes your code easier to read. • Debugging: if you annotate a function with a type, then make a typo in the body of the function, the compiler will tell you at compile-time that your function is wrong. • Types prevent errors
Reasons to use type signatures fiveOrSix :: Bool -> Int fiveOrSix True = 5 fiveOrSix False = 6 pairToInt :: (Bool, String) -> Int pairToInt x = fiveOrSix (fst x) • The function fiveOrSix takes a Bool. • When pairToInt receives its arguments, it knows, because of the type signature that the first element of the pair is a Bool. • Extract this using fst and pass that into fiveOrSix • This would work, because the type of the first element of the pair and the type of the argument to fiveOrSix are the same.
I/O • Performing input/output in a purely functional language like Haskell has long been a fundamental problem. • How to implement operations like getChar which returns the latest character that the user has typed or putChar c which prints the character c on the screen? • We somehow have to capture that getChar also performs the side effect of interacting with the user.
I/O • Haskell's I/O system is built around a mathematical foundation: the monad. • Monads are a conceptual structure into which I/O happens to fit. • I/O operations are seen as actions • Actions are defined rather than invoked within the expression language of Haskell.
I/O actions • The invocation of actions takes place outside of the expression evaluation • Actions are either atomic, as defined in system primitives, or are a sequential composition of other actions. • The I/O monad contains primitives which build composite actions, a process similar to joining statements in sequential order using ";" in other languages. • Thus the monad serves as the glue which binds together the actions in a program.
I/O actions • Every I/O action returns a value. • In the type system, the return value is "tagged" with IO type, distinguishing actions from other values. • The type of the function getChar is: getChar :: IO Char • The IO Char indicates that getChar, when invoked, performs some action which returns a character.
I/O actions • Actions which return no interesting values use the unit type, ( ). • For example, the putChar function: putChar :: Char -> IO () takes a character as an argument but returns nothing useful. • The unit type is similar to void in other languages.
do • Actions are sequenced using the operator >>= (or `bind'). • Instead of using this operator directly we can use the do notation • The keyword do introduces a sequence of statements which are executed in order. • A statement is: • an action • a pattern bound to the result of an action using <- • a set of local definitions introduced using let.
do A simple program to read and then print a character: main :: IO () main = do c <- getChar putChar c
do • We can invoke actions and examine their results using do, but how do we return a value from a sequence of actions? • Consider the ready function that reads a character and returns True if the character was a `y' ready :: IO Bool ready = do c <- getChar c == 'y' – Not correct !!!
return • This doesn't work because the second statement in the 'do' is just a boolean value, not an action. • We need to take this boolean and create an action that does nothing but return the boolean as its result. • The return function does just that: return :: a -> IO a ready :: IO Bool ready = do c <- getChar return (c == 'y')
Examples mypair :: IO (Char, Char) mypair = do x <- getChar y <- getChar return (x,y)
Examples Reads a string from the keyboard getline :: IO String getline = do x <- getChar if x == '\n' then return [ ] else do xs <- getLine return (x:xs)
Examples • An action that prompts for a string to be entered and displays its length: strlen :: IO () strlen = do putStr "Enter a string: " xs <- getLine putStr "The string has " putStr (show (length xs)) putStrLn " characters"
Examples doGuessing num = do putStrLn "Enter your guess:" guess <- getLine if (read guess) < num then do putStrLn "Too low!" doGuessing num else if (read guess) > num then do putStrLn "Too high!" doGuessing num else do putStrLn "You Win!"