260 likes | 441 Views
Abstract data types (ADTs). An ADT is a data type together with some functions to manipulate elements of this data type. It usually consists of a signature (interface), between the user and the implementor, an implementation which is (completely) hidden to the user.
E N D
Abstract data types (ADTs) An ADT is a data type together with some functions to manipulate elements of this data type. It usually consists of • a signature (interface), between the user and the implementor, • an implementation which is (completely) hidden to the user. Example: A calculator for numerical expressions. data Expr = Lit Int | IVar Var | Let Var Expr Expr | Expr :+: Expr | Expr :-: Expr | Expr :*: Expr | Expr :\: Expr
ADTs (cont’d) eval :: Expr -> Store -> Int eval (Lit n) store = n eval (IVar v) store = value store v eval (Let v e1 e2) store = eval e2 (update store v (eval e1 store)) eval (e1 :+: e2) store = eval e1 store + eval e2 store eval (e1 :-: e2) store = eval e1 store - eval e2 store eval (e1 :*: e2) store = eval e1 store * eval e2 store eval (e1 :\: e2) store = eval e1 store `div` eval e2 store execute :: Expr -> Int execute e = eval e initial
USER initial :: Store value :: Store -> Var -> Int update :: Store -> Var -> Int -> Store IMPLEMENTOR ADT Store The interface of Store consists of the functions / values • initial • value • update The implementation is hidden and can be exchanged.
ADT Store (1st implementation) module Store(Store,initial,value,update) where newtype Store = Sto [(Var,Int)] initial :: Store initial = Sto [] value :: Store -> Var -> Int value (Sto []) v = 0 value (Sto ((v',n):sto)) v | v == v' = n | otherwise = value (Sto sto) v update :: Store -> Var -> Int -> Store update (Sto sto) v n = Sto ((v,n):sto)
ADT Store (2nd implementation) module Store(Store,initial,value,update) where newtype Store = Sto (Var -> Int) initial :: Store initial = Sto (\v -> 0) value :: Store -> Var -> Int value (Sto sto) v = sto v update :: Store -> Var -> Int -> Store update (Sto sto) v n = Sto (\w -> if v==w then n else sto w)
Type classes We can declare ADTs as belonging to particular type classes. instance Eq Store where (Sto sto1) == (Sto sto2) = sto1==sto2 instance Show Store where show (Sto sto) = show sto Note, that once declared, these instances cannot be hidden, so that even though they are not named in the export list, the functions over Store which are defined by means of these instance declarations will be available whenever the module Store is imported.
USER type Queue a emptyQ :: Queue a isEmptyQ :: Queue a - > Bool addQ :: a -> Queue a -> Queue a remQ :: Queue a -> (a, Queue a) IMPLEMENTOR ADT Queue The interface for the ADT Queue:
ADT Queue (1st implementation) module Queue(Queue,emptyQ,isEmptyQ,addQ,remQ) where newtype Queue a = Qu [a] emptyQ :: Queue a emptyQ = Qu [] isEmptyQ :: Queue a -> Bool isEmptyQ (Qu xs) = null xs addQ :: a -> Queue a -> Queue a addQ x (Qu xs) = Qu (xs++[x]) remQ :: Queue a -> (a, Queue a) remQ q@(Qu xs) | not (isEmptyQ q) = (head xs, Qu (tail xs)) | otherwise = error ”remQ”
Properties of the implementation One characteristic of this implementation is • addQ is ‘expensive’, • remQ is ‘cheap’. Alternative: addQ x (Qu xs) = Qu (x:xs) remQ q@(Qu xs) | not (isEmptyQ q) = (last xs, Qu (init xs)) | otherwise = error ”remQ” But now we have: • addQ is ‘cheap’, • remQ is ‘expensive’.
ADT Queue (2nd implementation) module Queue(Queue,emptyQ,isEmptyQ,addQ,remQ) where data Queue a = Qu [a] [a] emptyQ :: Queue a emptyQ = Qu [] [] isEmptyQ :: Queue a -> Bool isEmptyQ (Qu [] []) = True isEmptyQ _ = False addQ :: a -> Queue a -> Queue a addQ x (Qu xs ys) = Qu xs (x:ys) remQ :: Queue a -> (a, Queue a) remQ (Qu (x:xs) ys) = (x, Qu xs ys) remQ (Qu [] (y:ys)) = remQ (Qu (reverse (y:ys)) []) remQ (Qu [] []) = error ”remQ”
Why is there no lengthQ function? Consider the following implementation of the function lengthQ computing the length of a queue: lengthQ :: Queue a -> Int lengthQ q | isEmpty q = 0 | otherwise = 1 + (lengthQ . snd . remQ) q The definition of lengthQ is independent of the implementation, and so would not have to be reimplemented if the implementation of the type Queue a is changed. This is a good reason for leaving lengthQ out of the signature.
Further examples of ADTs • the type Tree a of binary trees whose elements are of type a, • binary search trees as an extension of binary trees whose elements are ordered, • the type of sets, • the type of relations between a and b, • the type of a graph with node from a (special case of a relation).
Parsing Consider again the following data type for expressions: data Expr = Lit Int | IVar Var | Let Var Expr Expr | Expr :+: Expr | Expr :-: Expr | Expr :*: Expr | Expr :\: Expr The class Read can be used to generate elements of type Expr given a string representation. Notice, that the derived read function would accept strings as ”Lit 4 :\: Lit 2” rather than the natural representation ”4 \ 2”.
A type for parsers First attempt: type Parse1 a b = [a] -> b Suppose that bracket and number are parsers of this type which recognize brackets and numbers: bracket ”(xyz” ‘(‘ number ”234” 2 or 23 or 234 bracket ”234” no result? Second attempt: type Parse2 a b = [a] -> [b] bracket ”(xyz” [‘(‘] number ”234” [2,23,234] bracket ”234” []
A type for parsers (cont’d) Final attempt: type Parse a b = [a] -> [(b,[a])] bracket ”(xyz” [(‘(‘,”xyz”)] number ”234” [(2,”34”),(23,”4”),(234,””)] bracket ”234” []
Some basic parsers none :: Parse a b none inp = [] succeed :: b -> Parse a b succeed val inp = [(val,inp)] token :: Eq a => a -> Parse a a token t (x:xs) | t==x = [(t,xs)] | otherwise = [] token t [] = [] spot :: (a -> Bool) -> Parse a a spot p (x:xs) | p x = [(x,xs)] | otherwise = [] spot p [] = []
Some basic parsers (cont’d) Some examples: bracket :: Parse Char Char bracket = token ‘(‘ bracket ”(xyz” [(‘(‘,”xyz”)] dig :: Parse Char Char dig = spot isDigit dig ”23df” [(‘2‘,”3df”)]
Combining Parsers alt p1 p2 recognizes anything recognized by p1 or by p2. alt :: Parse a b -> Parse a b -> Parse a b alt p1 p2 inp = p1 inp ++ p2 inp (bracket `alt` dig) ”234” Apply one parser then the second to the result(s) of the first. infixr 5 >*> (>*>) :: Parse a b -> Parse a c -> Parse a (b,c) (>*>) p1 p2 inp = [((y,z),rem2) | (y,rem1) <- p1 inp , (z,rem2) <- p2 rem1 ] (bracket >*> dig) ”(234” [((‘(‘,‘2‘),”34”)]
Combining Parsers (cont’d) Transform the results of the parses according to the function. build :: Parse a b -> (b -> c) -> Parse a c build p f inp = [ (f x,rem) | (x,rem) <- p inp ] (dig `build` char2int) ”23df” [(2,”3df”)] infixr 5 .*> (.*>) :: Parse a b -> Parse a c -> Parse a c p1 .*> p2 = (p1 >*> p2) `build` snd infixr 5 >*. (>*.) :: Parse a b -> Parse a c -> Parse a b p1 >*. p2 = (p1 >*> p2) `build` fst
Combining Parsers (cont’d) Recognize a list of objects. list :: Parse a b -> Parse a [b] list p = (succeed []) `alt` ((p >*> list p) `build` convert) where convert = uncurry (:) list dig ”234(” [(””,”234(”),(”2”,”34(”),(”23”,”4(”),(”234”,”(”)] stringToken :: String -> Parse Char String stringToken s1 s2 = [ (a,b) | (a,b) <- lex s2, a==s1]
Parsing expressions Grammar for expressions: Expr = Int | Char | let Char = Expr in Expr | (Expr + Expr) | (Expr - Expr) | (Expr * Expr) | (Expr div Expr) Example: (4 + 3) div 2
Parsing expressions parseVar = spot (\x -> 'a' <= x && x <= 'z') parser = (reads `build` Lit) `alt` (parseVar `build` IVar) `alt` ((stringToken "let" .*> parseVar >*> stringToken "=" .*> parser >*> stringToken "in" .*> parser) `build` (\(v,(e1,e2)) -> Let v e1 e2)) `alt` ((stringToken "(" .*> parser >*> stringToken "+" .*> parser >*. stringToken ")") `build` (uncurry (:+:))) `alt` ((stringToken "(" .*> parser >*> stringToken "-" .*> parser >*. stringToken ")") `build` (uncurry (:-:))) `alt` ((stringToken "(" .*> parser >*> stringToken "*" .*> parser >*. stringToken ")") `build` (uncurry (:*:))) `alt` ((stringToken "(" .*> parser >*> stringToken "div" .*> parser >*. stringToken ")") `build` (uncurry (:\:)))
Parsing expression (cont’d) parse str = if null p then error ”Parse error: no parse” else if snd e /= ”” then error ”Parse error: unexpected end of input” else fst e where p = parser str e = head p instance Read Expr where readsPrec n = parser
Further considerations Alternative grammar for expressions: Expr = Int | Char | let Char = Expr in Expr | (Expr) | Expr + Expr | Expr - Expr | Expr * Expr | Expr div Expr A similar translation into a Haskell program does not work !!! Reason: the grammar above is left-recursive.
Further considerations (cont’d) Solution: Modify grammar as follows: Expr = Expr1 | Expr1 + Expr | Expr1 - Expr | Expr1 * Expr | Expr1 div Expr Expr1 = Int | Char | let Char = Expr in Expr | (Expr)
Using Parsec parser = do { n <- read2Parsec; -- function in Basics.hs return $ Lit n } <|> … <|> do { strings “(”; -- function in Bascics.hs -- Parsec only defines -- string :: String -> ... t1 <- parser; spaces; strings “-”; t2 <- parser; spaces; strings “)”; return $ t1 :-: t2 }