0 PROGRAMMING IN HASKELL Chapter 11 - Interactive Programs, Declaring Types and Classes.

Download 0 PROGRAMMING IN HASKELL Chapter 11 - Interactive Programs, Declaring Types and Classes.

Post on 28-Dec-2015




3 download


  • *PROGRAMMING IN HASKELLChapter 11 - Interactive Programs,Declaring Types and Classes

  • Introduction*To date, we have seen how Haskell can be used to write batch programs that take all their inputs at the start and give all their outputs at the end.

  • *However, we would also like to use Haskell to write interactive programs that read from the keyboard and write to the screen, as they are running.

  • The Problem*Haskell programs are pure mathematical functions:However, reading from the keyboard and writing to the screen are side effects:Haskell programs have no side effects.Interactive programs have side effects.

  • The Solution*Interactive programs can be written in Haskell by using types to distinguish pure expressions from impure actions that may involve side effects.IO aThe type of actions that return a value of type a.

  • *For example:IO CharIO ()The type of actions that return a character.The type of purely side effecting actions that return no result value.() is the type of tuples with no components.Note:

  • Basic Actions*The standard library provides a number of actions, including the following three primitives:getChar :: IO CharThe action getChar reads a character from the keyboard, echoes it to the screen, and returns the character as its result value:

  • *The action putChar c writes the character c to the screen, and returns no result value:putChar :: Char IO ()The action return v simply returns the value v, without performing any interaction:return :: a IO a

  • Sequencing*A sequence of actions can be combined as a single composite action using the keyword do.

    For example:a :: IO (Char,Char)a = do x getChar getChar y getChar return (x,y)

  • Derived Primitives*getLine :: IO StringgetLine = do x getChar if x == '\n' then return [] else do xs getLine return (x:xs)Reading a string from the keyboard:

  • *putStr :: String IO ()putStr [] = return ()putStr (x:xs) = do putChar x putStr xsWriting a string to the screen:Writing a string and moving to a new line:putStrLn :: String IO ()putStrLn xs = do putStr xs putChar '\n'

  • Example*We can now define 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"

  • *For example:> strlen

    Enter a string: abcdeThe string has 5 charactersEvaluating an action executes its side effects, with the final result value being discarded.Note:

  • Hangman*Consider the following version of hangman:One player secretly types in a word.

    The other player tries to deduce the word, by entering a sequence of guesses.

    For each guess, the computer indicates which letters in the secret word occur in the guess.

  • *The game ends when the guess is correct.hangman :: IO ()hangman = do putStrLn "Think of a word: " word sgetLine putStrLn "Try to guess it:" guess wordWe adopt a top down approach to implementing hangman in Haskell, starting as follows:

  • *The action sgetLine reads a line of text from the keyboard, echoing each character as a dash:sgetLine :: IO StringsgetLine = do x getCh if x == '\n' then do putChar x return [] else do putChar '-' xs sgetLine return (x:xs)

  • *primitive getCh :: IO CharNote:The action getCh reads a character from the keyboard, without echoing it to the screen.

    This useful action is not part of the standard library, but is a special Hugs primitive that can be imported into a script as follows:

  • *The function guess is the main loop, which requests and processes guesses until the game ends.guess :: String IO ()guess word = do putStr "> " xs getLine if xs == word then putStrLn "You got it!" else do putStrLn (diff word xs) guess word

  • *The function diff indicates which characters in one string occur in a second string:For example:> diff "haskell" "pascal" "-as--ll"diff :: String String Stringdiff xs ys = [if elem x ys then x else '-' | x xs]

  • Exercise*Implement the game of nim in Haskell, where the rules of the game are as follows:The board comprises five rows of stars:1: * * * * *2: * * * *3: * * *4: * *5: *

  • *Two players take it turn about to remove one or more stars from the end of a single row.

    The winner is the player who removes the last star or stars from the board.Hint:

    Represent the board as a list of five integers that give the number of stars remaining on each row. For example, the initial board is [5,4,3,2,1].

  • Type Declarations*In Haskell, a new name for an existing type can be defined using a type declaration.type String = [Char]String is a synonym for the type [Char].

  • *Type declarations can be used to make other types easier to read. For example, givenorigin :: Posorigin = (0,0)

    left :: Pos Posleft (x,y) = (x-1,y)type Pos = (Int,Int)we can define:

  • *Like function definitions, type declarations can also have parameters. For example, giventype Pair a = (a,a)we can define:mult :: Pair Int Intmult (m,n) = m*n

    copy :: a Pair acopy x = (x,x)

  • *Type declarations can be nested:type Pos = (Int,Int)

    type Trans = Pos PosHowever, they cannot be recursive:type Tree = (Int,[Tree])

  • Data Declarations*A completely new type can be defined by specifying its values using a data declaration.data Bool = False | TrueBool is a new type, with two new values False and True.

  • *Note:The two values False and True are called the constructors for the type Bool.

    Type and constructor names must begin with an upper-case letter.

    Data declarations are similar to context free grammars. The former specifies the values of a type, the latter the sentences of a language.

  • *answers :: [Answer]answers = [Yes,No,Unknown]

    flip :: Answer Answerflip Yes = Noflip No = Yesflip Unknown = Unknowndata Answer = Yes | No | Unknownwe can define:Values of new types can be used in the same ways as those of built in types. For example, given

  • *The constructors in a data declaration can also have parameters. For example, givendata Shape = Circle Float | Rect Float Floatsquare :: Float Shapesquare n = Rect n n

    area :: Shape Floatarea (Circle r) = pi * r^2area (Rect x y) = x * ywe can define:

  • *Note:Shape has values of the form Circle r where r is a float, and Rect x y where x and y are floats.

    Circle and Rect can be viewed as functions that construct values of type Shape:Circle :: Float Shape

    Rect :: Float Float Shape

  • *Not surprisingly, data declarations themselves can also have parameters. For example, givendata Maybe a = Nothing | Just asafediv :: Int Int Maybe Intsafediv _ 0 = Nothingsafediv m n = Just (m `div` n)

    safehead :: [a] Maybe asafehead [] = Nothingsafehead xs = Just (head xs)we can define:

  • Recursive Types*In Haskell, new types can be declared in terms of themselves. That is, types can be recursive.data Nat = Zero | Succ NatNat is a new type, with constructors Zero :: Nat and Succ :: Nat Nat.

  • *Note:A value of type Nat is either Zero, or of the form Succ n where n :: Nat. That is, Nat contains the following infinite sequence of values:ZeroSucc ZeroSucc (Succ Zero)

  • *We can think of values of type Nat as natural numbers, where Zero represents 0, and Succ represents the successor function 1+.

    For example, the valueSucc (Succ (Succ Zero))represents the natural number

  • *Using recursion, it is easy to define functions that convert between values of type Nat and Int:nat2int :: Nat Intnat2int Zero = 0nat2int (Succ n) = 1 + nat2int n

    int2nat :: Int Natint2nat 0 = Zeroint2nat (n+1) = Succ (int2nat n)

  • *Two naturals can be added by converting them to integers, adding, and then converting back:However, using recursion the function add can be defined without the need for conversions:add :: Nat Nat Natadd m n = int2nat (nat2int m + nat2int n)add Zero n = nadd (Succ m) n = Succ (add m n)

  • *For example:add (Succ (Succ Zero)) (Succ Zero)

  • Arithmetic Expressions*Consider a simple form of expressions built up from integers using addition and multiplication.

  • *Using recursion, a suitable new type to represent such expressions can be declared by:For example, the expression on the previous slide would be represented as follows:data Expr = Val Int | Add Expr Expr | Mul Expr ExprAdd (Val 1) (Mul (Val 2) (Val 3))

  • *Using recursion, it is now easy to define functions that process expressions. For example:size :: Expr Intsize (Val n) = 1size (Add x y) = size x + size ysize (Mul x y) = size x + size y

    eval :: Expr Inteval (Val n) = neval (Add x y) = eval x + eval yeval (Mul x y) = eval x * eval y

  • *Note:The three constructors have types:Val :: Int ExprAdd :: Expr Expr ExprMul :: Expr Expr ExprMany functions on expressions can be defined by replacing the constructors by other functions using a suitable fold function. For example:eval = fold id (+) (*)

  • Binary Trees*In computing, it is often useful to store data in a two-way branching structure or binary tree.

  • *Using recursion, a suitable new type to represent such binary trees can be declared by:For example, the tree on the previous slide would be represented as follows:data Tree = Leaf Int | Node Tree Int TreeNode (Node (Leaf 1) 3 (Leaf 4)) 5 (Node (Leaf 6) 7 (Leaf 9))

  • *We can now define a function that decides if a given integer occurs in a binary tree:occurs :: Int Tree Booloccurs m (Leaf n) = m==noccurs m (Node l n r) = m==n || occurs m l || occurs m rBut in the worst case, when the integer does not occur, this function traverses the entire tree.

  • *Now consider the function flatten that returns the list of all the integers contained in a tree:flatten :: Tree [Int]flatten (Leaf n) = [n]flatten (Node l n r) = flatten l ++ [n] ++ flatten rA tree is a search tree if it flattens to a list that is ordered. Our example tree is a search tree, as it flattens to the ordered list [1,3,4,5,6,7,9].

  • *Search trees have the important property that when trying to find a value in a tree we can always decide which of the two sub-trees it may occur in:This new definition is more efficient, because it only traverses one path down the tree.occurs m (Leaf n) = m==noccurs m (Node l n r) | m==n = True | mn = occurs m r

  • Exercises*



View more >