Algebraic Data Types
Table of Contents
1 Algebraic Data Types
[Under Construction]
1.1 Overview
Algebraic data type is a type formed by combining types. It can be used to implement Records, Enumerated Sets, option types and Recursive types. Note: Algebraic data types must not be confused with Abstract data types.
- Applications of Algebraic Data Types
- Build Embedded Domain specific languages - EDSLs
- Build Interpreters
- Self Documenting code
- Avoid Null checking error / Null reference bug
- Product Types
- Tuples of types - (Int, Float)
- Records
- Sum Types
- Tagged or disjoint unions or variant types
- Enumerated types
- Option Type
Option type is useful in functional programming to avoid boilerplate null checking bug on non-deterministic computations.
Language | Syntax | |
---|---|---|
Haskell | data Maybe a = Nothing | Just a |
OCaml and F# | type 'a option = None | Some of 'a |
Standard ML | datatype 'a option = NONE | SOME of 'a |
Java 8 | the option type is defined as parameterized final class Optional<T> |
See also:
- The "Designing with types" series - F# for fun and for profiting
- F# : Option Types
- Domain Driven Design
Selected Wikipedia Pages
1.2 Enumerated Data Type
Example: Days of Week
Enumerated sets is type which can only have a limited number of values.
data Weekday = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Eq, Ord, Enum) fromDay :: Weekday -> Int fromDay = fromEnum toDay :: Int -> Weekday toDay = toEnum > map (Monday<) [Tuesday, Friday, Sunday] [True,True,True] > map (Thursday<) [Monday, Tuesday, Friday, Sunday] [False,False,True,True] > > Monday == Tuesday False > Tuesday == Tuesday True > > > fromDay Saturday 5 > 1 + fromDay Monday 1 > 1 + fromDay Saturday 6 > Saturday Saturday > > toDay 0 Monday > toDay 6 Sunday > >
Example: Colors
data Color = Red | Orange | Yellow | Green | Blue | Purple | White | Black | CustomColor Int Int Int -- R G B components deriving (Eq) colorToRGB Red = (255,0,0) colorToRGB Orange = (255,128,0) colorToRGB Yellow = (255,255,0) colorToRGB Green = (0,255,0) colorToRGB Blue = (0,0,255) colorToRGB Purple = (255,0,255) colorToRGB White = (255,255,255) colorToRGB Black = (0,0,0) colorToRGB (CustomColor r g b) = (r,g,b) -- this one is new > > Red == White False > > Red == Red True > > let b = CustomColor 120 240 100 > colorToRGB b (120,240,100) > map colorToRGB [ Blue, White, Yellow ] [(0,0,255),(255,255,255),(255,255,0)] >
Example: Shapes
data Shape = Circle Float | Rect Float Float deriving Show square :: Float -> Shape square n = Rect n n area :: Shape -> Float area (Circle r) = pi * r^2 area (Rect x y) = x * y is_circle (Circle _ ) = True is_circle _ = False is_rect (Rect _ _) = True is_rect _ = False describe (Circle radius) = putStrLn $ "Circle of radius : " ++ show(radius) describe (Rect h w) = putStrLn $ "Rectangle of " ++ show(h) ++ " x " ++ show(w) > area $ Rect 20 30 600.0 > area $ Circle 20 1256.6371 > area $ square 20 400.0 > > [Rect 20.0 30.0, Rect 10.0 30.0, Circle 3.0, Circle 4.0] [Rect 20.0 30.0,Rect 10.0 30.0,Circle 3.0,Circle 4.0] > > map area [Rect 20.0 30.0, Rect 10.0 30.0, Circle 3.0, Circle 4.0] [600.0,300.0,28.274334,50.265484] > > :t is_rect is_rect :: Shape -> Bool > :t is_circle is_circle :: Shape -> Bool > > filter is_rect [Rect 20.0 30.0, Rect 10.0 30.0, Circle 3.0, Circle 4.0] [Rect 20.0 30.0,Rect 10.0 30.0] > filter is_circle [Rect 20.0 30.0, Rect 10.0 30.0, Circle 3.0, Circle 4.0] [Circle 3.0,Circle 4.0] > :t describe describe :: Shape -> IO () > > describe (Rect 20 30) Rectangle of 20.0 x 30.0 > describe (Circle 3.60) Circle of radius : 3.6 > > mapM_ describe [Rect 20.0 30.0, Rect 10.0 30.0, Circle 3.0, Circle 4.0] Rectangle of 20.0 x 30.0 Rectangle of 10.0 x 30.0 Circle of radius : 3.0 Circle of radius : 4.0 > > let describe_list = mapM_ describe > > describe_list [Rect 20.0 30.0, Rect 10.0 30.0, Circle 3.0, Circle 4.0] Rectangle of 20.0 x 30.0 Rectangle of 10.0 x 30.0 Circle of radius : 3.0 Circle of radius : 4.0 >
1.3 Typeclass without Record Syntax
Example: Students GPA
data Student = USU String Float deriving (Show) get_gpa :: Student -> Float get_gpa (USU _ grade) = grade get_name :: Student -> String get_name (USU name _ ) = name class_gpa :: [Student] -> Float class_gpa myclass = (sum c) / fromIntegral (length c) where c = map get_gpa myclass > let myke = USU "Mike" 4.0 > > get_name myke "Mike" > get_gpa myke 4.0 > > let myclass = [USU "Mike" 3.7, USU "Steve" 3.9, USU "Fred" 2.9, USU "Joe" 1.5] > > class_gpa myclass 3.0
1.4 Record Syntax
Example: Typeclass with record Syntax
data Person = Person { firstName :: String, lastName :: String, age :: Int } deriving (Eq, Show, Read) people = [ Person { firstName = "Ayn", lastName = "Rand", age =50}, Person { firstName = "John", lastName = "Galt", age =28}, Person { firstName = "Adam", lastName = "Smith", age =70}] {- Get someone from the people database -} getPerson n = people !! n {- Show Person -} showPerson :: Person -> String showPerson person = "Name: " ++ show(firstName person) ++ " - Last Name: " ++ show(lastName person) ++ " - Age " ++ show(age person) > people [Person {firstName = "Ayn", lastName = "Rand", age = 50},Person {firstName = "John", lastName = "Galt", age = 28},Person {firstName = "Adam", lastName = "Smith", age = 70}] > > map firstName people ["Ayn","John","Adam"] > > map (\el -> fst el ++ " " ++ snd el) $ zip (map firstName people) (map lastName people) > ["Ayn Rand","John Galt","Adam Smith"] > people !! 1 Person {firstName = "John", lastName = "Galt", age = 28} > people !! 2 Person {firstName = "Adam", lastName = "Smith", age = 70} > > "person 0 is " ++ show (people !! 0) "person 0 is Person {firstName = \"Ayn\", lastName = \"Rand\", age = 50}" > > "person 1 is " ++ show (people !! 1) "person 1 is Person {firstName = \"John\", lastName = \"Galt\", age = 28}" > > let person = read "Person {firstName =\"Elmo\", lastName =\"NA\", age = 0}" :: Person > person Person {firstName = "Elmo", lastName = "NA", age = 0} > > firstName person "Elmo" > last last lastName > lastName person "NA" > age person 0 > > let tesla = Person { firstName = "Nikola", lastName = "Tesla", age =30} > tesla Person {firstName = "Nikola", lastName = "Tesla", age = 30} > > showPerson tesla "Name: \"Nikola\" - Last Name: \"Tesla\" - Age 30" >
1.5 Recursive Data Types
Example: Custom List Implementation
File: list_cons.hs
data List a = Nil | Cons a (List a) deriving (Show) is_empty :: List t -> Bool is_empty Nil = True is_empty _ = False is_empty2 lst = case lst of Nil -> True _ -> False count :: List t -> Int count lst = case lst of Nil -> 0 Cons a next_lst -> 1 + count next_lst suml lst = case lst of Nil -> 0 Cons a next_lst -> a + suml next_lst headl (Cons a _) = a headl Nil = error "Empty List" safe_headl (Cons a _) = Just a safe_headl Nil = Nothing lastl Nil = error "Failed: Empty List" lastl (Cons a Nil) = a lastl (Cons _ t) = lastl t {- Converts to Haskell List -} to_list Nil = [] to_list (Cons x xs) = x:(to_list xs) takel n lst = case (n, lst) of (_, Nil ) -> Nil (0, _ ) -> Nil (k, Cons h t) -> Cons h (takel (n-1) t) nth lst n = case (n, lst) of (_, Nil) -> error "Index too large" (0, Cons a _) -> a (k, Cons h t) -> nth t (n-1) mapl f Nil = Nil mapl f (Cons h t) = Cons (f h) (mapl f t) filterl f Nil = Nil filterl f (Cons h t) = if f h then Cons h (filterl f t) else filterl f t {- Empty List [] -} list0 = Nil {- Single element list -} list1 = Cons 10 Nil list2 = Cons "Haskell" Nil list3 = Cons 10 (Cons 20 (Cons 30 Nil)) list4 = Cons 1.25 (Cons 0.65 (Cons 8.123 ( Cons 9.434 Nil)))
Running in ghci
> :l list_cons.hs [1 of 1] Compiling Main ( list_cons.hs, interpreted ) Ok, modules loaded: Main. > :t is_empty is_empty :: List t -> Bool > :t count count :: List t -> Int > list0 Nil > list1 Cons 10 Nil > list2 Cons "Haskell" Nil > list3 Cons 10 (Cons 20 (Cons 30 Nil)) > list4 Cons 1.25 (Cons 0.65 (Cons 8.123 (Cons 9.434 Nil))) > :t list0 list0 :: List a > :t list1 list1 :: List Integer > :t list2 list2 :: List [Char] > :t list3 list3 :: List Integer > :t list4 list4 :: List Double > Cons 10.23 list0 Cons 10.23 Nil > > Cons 30 list1 Cons 30 (Cons 10 Nil) > > Cons 40 list3 Cons 40 (Cons 10 (Cons 20 (Cons 30 Nil))) > > Cons 50 (Cons 40 list3) Cons 50 (Cons 40 (Cons 10 (Cons 20 (Cons 30 Nil)))) > is_empty list0 True > is_empty list1 False > is_empty list2 False > is_empty list3 False > > is_empty2 list0 True > is_empty2 list1 False > is_empty2 list3 False > > count list0 0 > count list1 1 > count list2 1 > count list3 3 {- Getting the first element -} > headl list0 *Example: Custom List Implementation* File: list_cons.hs #+BEGIN_SRC haskell data List a = Nil | Cons a (List a) deriving (Show) is_empty :: List t -> Bool is_empty Nil = True is_empty _ = False is_empty2 lst = case lst of Nil -> True _ -> False count :: List t -> Int count lst = case lst of Nil -> 0 Cons a next_lst -> 1 + count next_lst suml lst = case lst of Nil -> 0 Cons a next_lst -> a + suml next_lst headl (Cons a _) = a headl Nil = error "Empty List" safe_headl (Cons a _) = Just a safe_headl Nil = Nothing lastl Nil = error "Failed: Empty List" lastl (Cons a Nil) = a lastl (Cons _ t) = lastl t {- Converts to Haskell List -} to_list Nil = [] to_list (Cons x xs) = x:(to_list xs) takel n lst = case (n, lst) of (_, Nil ) -> Nil (0, _ ) -> Nil (k, Cons h t) -> Cons h (takel (n-1) t) nth lst n = case (n, lst) of (_, Nil) -> error "Index too large" (0, Cons a _) -> a (k, Cons h t) -> nth t (n-1) mapl f Nil = Nil mapl f (Cons h t) = Cons (f h) (mapl f t) filterl f Nil = Nil filterl f (Cons h t) = if f h then Cons h (filterl f t) else filterl f t {- Empty List [] -} list0 = Nil {- Sigle element list -} list1 = Cons 10 Nil list2 = Cons "Haskell" Nil list3 = Cons 10 (Cons 20 (Cons 30 Nil)) list4 = Cons 1.25 (Cons 0.65 (Cons 8.123 ( Cons 9.434 Nil))) ``` Running in ghci ```haskell > :l list_cons.hs [1 of 1] Compiling Main ( list_cons.hs, interpreted ) Ok, modules loaded: Main. > :t is_empty is_empty :: List t -> Bool > :t count count :: List t -> Int > list0 Nil > list1 Cons 10 Nil > list2 Cons "Haskell" Nil > list3 Cons 10 (Cons 20 (Cons 30 Nil)) > list4 Cons 1.25 (Cons 0.65 (Cons 8.123 (Cons 9.434 Nil))) > :t list0 list0 :: List a > :t list1 list1 :: List Integer > :t list2 list2 :: List [Char] > :t list3 list3 :: List Integer > :t list4 list4 :: List Double > Cons 10.23 list0 Cons 10.23 Nil > > Cons 30 list1 Cons 30 (Cons 10 Nil) > > Cons 40 list3 Cons 40 (Cons 10 (Cons 20 (Cons 30 Nil))) > > Cons 50 (Cons 40 list3) Cons 50 (Cons 40 (Cons 10 (Cons 20 (Cons 30 Nil)))) > is_empty list0 True > is_empty list1 False > is_empty list2 False > is_empty list3 False > > is_empty2 list0 True > is_empty2 list1 False > is_empty2 list3 False > > count list0 0 > count list1 1 > count list2 1 > count list3 3 {- Getting the first element -} > headl list0 *** Exception: Empty List > headl list1 10 > headl list2 "Haskell" > headl list3 10 > headl list4 1.25 > {- Safe version of headl -} > safe_headl list0 Nothing > safe_headl list1 Just 10 > safe_headl list2 Just "Haskell" > safe_headl list3 Just 10 > safe_headl list4 Just 1.25 > > :t safe_headl safe_headl :: List a -> Maybe a > {- Getting the last element -} > lastl list0 *** Exception: Failed: Empty List > lastl list1 10 > lastl list2 "Haskell" > lastl list3 30 > lastl list4 9.434 > {- Converting to Standard Haskell List -} > to_list list0 [] > to_list list1 [ 10 ] > to_list list2 ["Haskell"] > to_list list3 [10,20,30] > to_list list4 [1.25,0.65,8.123,9.434] > :t to_list to_list :: List a -> [a] > takel 0 Nil Nil > takel 10 Nil Nil > takel 0 list4 Nil > takel 1 list4 Cons 1.25 Nil > takel 2 list4 Cons 1.25 (Cons 0.65 Nil) > takel 3 list4 Cons 1.25 (Cons 0.65 (Cons 8.123 Nil)) > takel 4 list4 Cons 1.25 (Cons 0.65 (Cons 8.123 (Cons 9.434 Nil))) > takel 5 list4 Cons 1.25 (Cons 0.65 (Cons 8.123 (Cons 9.434 Nil))) > takel 6 list4 Cons 1.25 (Cons 0.65 (Cons 8.123 (Cons 9.434 Nil))) > takel 26 list4 Cons 1.25 (Cons 0.65 (Cons 8.123 (Cons 9.434 Nil))) {- Infinite List -} > let ones = Cons 1 ones > > takel 1 ones Cons 1 Nil > > takel 3 ones Cons 1 (Cons 1 (Cons 1 Nil)) > > takel 5 ones Cons 1 (Cons 1 (Cons 1 (Cons 1 (Cons 1 Nil)))) > let from n = Cons n (from (n+1)) > takel 3 (from 0) Cons 0 (Cons 1 (Cons 2 Nil)) > takel 3 (from 3) Cons 3 (Cons 4 (Cons 5 Nil)) > takel 10 (from 8) Cons 8 (Cons 9 (Cons 10 (Cons 11 (Cons 12 (Cons 13 (Cons 14 (Cons 15 (Cons 16 (Cons 17 Nil))))))))) > let from_step start step = Cons start (from_step (start+step) step) > takel 3 (from_step 1 3) Cons 1 (Cons 4 (Cons 7 Nil)) > > takel 6 (from_step 2 4) Cons 2 (Cons 6 (Cons 10 (Cons 14 (Cons 18 (Cons 22 Nil))))) > takel 8 (from_step 0 1) Cons 0 (Cons 1 (Cons 2 (Cons 3 (Cons 4 (Cons 5 (Cons 6 (Cons 7 Nil))))))) > nth (from_step 0 1) 0 0 > nth (from_step 0 1) 1 1 > nth (from_step 0 1) 2 2 > nth (from_step 0 1) 6 6 > nth (from_step 0 1) 22 22 > nth (from_step 0 1) 60 60 > > > :t nth nth :: (Eq t, Num t) => List t1 -> t -> t1 > > mapl (\x -> 3*x + 5) list0 Nil > mapl (\x -> 3*x + 5) list3 Cons 35 (Cons 65 (Cons 95 Nil)) > mapl (\x -> 3*x + 5) list4 Cons 8.75 (Cons 6.95 (Cons 29.369 (Cons 33.302 Nil))) > > :t mapl mapl :: (t -> a) -> List t -> List a > > list4 Cons 1.25 (Cons 0.65 (Cons 8.123 (Cons 9.434 Nil))) > > filterl (\x -> x > 3.5) list4 Cons 8.123 (Cons 9.434 Nil) > > filterl (\x -> x > 8) list4 Cons 8.123 (Cons 9.434 Nil) > > :t filterl filterl :: (a -> Bool) -> List a -> List a
Reference: