Making Data Types ================= .. note:: :doc:`This is a literate Haskell page: you can load it directly into ghci by following these steps <literate_haskell>`. Haskell_ has a number of ways to create new types. For instance, you can create a new type consisting of finite values like this:: > data Semester = Spring | Summer | Fall > deriving (Show, Eq, Enum) This is similar to enumerated types in languages like C++ or Java. The ``deriving`` statement adds some convenience functions: ``Show`` converts the type values to strings (of the same name), ``Eq`` allows for comparisons with ``==`` and ``\=``, and ``Enum`` allows the use of functions like ``succ`` (successor) and ``pred`` (predecessor). For example:: > show Fall "Fall" > Spring == Summer False > Spring /= Summer True > succ Spring Summer > pred Spring *** Exception: pred{Semester}: tried to take `pred' of first tag in enumeration > pred Fall Summer A **type synonym** is another name for an existing type, and it works essentially the same way as ``typedef`` in C/C++. In Haskell_, use ``type`` to create a type synonym:: > type Year = Int -- type synonym > > data Campus = Burnaby | Surrey | Vancouver > deriving (Show, Eq, Enum) > > type CourseOffering = (Year, Semester, Campus) -- type synonym Here, ``CourseOffering`` is another name for the type ``(Year, Semester, Campus)``. Algebraic Data Types -------------------- Types created with ``data`` are called **algebraic data types**, and they go well beyond the enumerations of C++ or Java_. For instance, consider this type:: > data Shape = Circle Float | Rectangle Float Float > deriving (Show) ``Shape`` is a type that denotes either a ``Circle`` or a ``Rectangle`` object. A ``Circle`` takes one ``Float`` value (representing it's radius) as input, while a ``Rectangle`` takes two ``Float`` values (its width and height). ``Circle`` and ``Rectangle`` are called **value constructors**. For example:: > area :: Shape -> Float > area (Circle radius) = pi * radius ^ 2 -- pi is pre-defined in Haskell > area (Rectangle width height) = width * height > area (Circle 2) 12.56 > area (Rectangle 4 6) 24.0 Parameterized Types ------------------- Haskell_ also lets you create types that take a type as input, e.g.:: data Maybe a = Nothing | Just a -- pre-defined in Haskell Here, ``a`` is a type variable that can be *any* type. The idea is that values of type ``Maybe a`` could be some value of type ``a``, or they could be nothing at all. For instance, you could use ``Maybe`` to represent a list of values, where some of the values are unknown:: > let lst = [Just 3, Just 6, Nothing, Just 2] > lst [Just 3,Just 6,Nothing,Just 2] > :type lst lst :: [Maybe Integer] Here's how you could sum a list of ``Maybe Integer``\ s:: > maybeSum :: [Maybe Integer] -> Integer > maybeSum [] = 0 > maybeSum (Nothing:xs) = maybeSum xs > maybeSum ((Just x):xs) = x + (maybeSum xs) The basic idea is that for each value on the list we check to see if it is ``Nothing``, or if it is an integer. We've decided to treat ``Nothing`` as equivalent to 0 here, but for other functions it might make more sense to use a different value, e.g. for a ``maybeProduct`` function it would make more sense to treat ``Nothing``\ s as equivalent to 1. You can use ``Maybe`` in calculations where the result might fail, e.g.:: > safeInvert :: Float -> Maybe Float > safeInvert 0.0 = Nothing > safeInvert x = Just (1.0 / x) For instance:: > safeInvert 2 Just 0.5 > safeInvert 0 Nothing However, you can't directly use the result of ``invert`` in calculations because it returns values of type ``Maybe Float``, not ``Float``. You are forced to handle the case when the result is ``Nothing``, which, while perhaps tedious, ensures errors are not ignored. > maybeAdd :: Maybe Float -> Maybe Float -> Maybe Float > maybeAdd Nothing _ = Nothing > maybeAdd _ Nothing = Nothing > maybeAdd (Just x) (Just y) = Just (x + y) For example:: > maybeAdd (safeInvert 2) (safeInvert 2) Just 1.0 > maybeAdd (safeInvert 2) (safeInvert 0) Nothing Recursive Data Structures ------------------------- In addition to type parameters, you can write recursive data types such as ``BST`` (short for `binary search tree <https://en.wikipedia.org/wiki/Binary_search_tree>`_):: > data BST a = EmptyBST | Node a (BST a) (BST a) > deriving (Show, Read, Eq) This says that a ``BST`` is either empty (``EmptyBST``), or it is a ``Node`` consisting of a value of type ``a`` (i.e. the data stored in the node), and two trees of type ``BST a`` (i.e. the left and right subtrees of the node). For example:: > bstInsert :: Ord a => BST a -> a -> BST a > bstInsert EmptyBST x = (Node x EmptyBST EmptyBST) > bstInsert (Node val left right) x = if x < val then > Node val (bstInsert left x) right > else > Node val left (bstInsert right x) :: > bstInsert (bstInsert EmptyBST 5) 9 Node 5 EmptyBST (Node 9 EmptyBST EmptyBST) > bstInsert (bstInsert (bstInsert EmptyBST 5) 9) 2 Node 5 (Node 2 EmptyBST EmptyBST) (Node 9 EmptyBST EmptyBST) > bstInsert (bstInsert (bstInsert (bstInsert EmptyBST 5) 9) 2) 3 Node 5 (Node 2 EmptyBST (Node 3 EmptyBST EmptyBST)) (Node 9 EmptyBST EmptyBST) ``bstContains`` tests if a value is a BST or not:: > bstContains :: Ord a => BST a -> a -> Bool > bstContains EmptyBST _ = False > bstContains (Node val left right) x > | val == x = True > | val < x = bstContains right x > | val > x = bstContains left x For example:: > let t = bstInsert (bstInsert (bstInsert (bstInsert EmptyBST 5) 9) 2) 3 > bstContains t 3 True > bstContains t 4 False An inorder traversal of a BST visits the elements in ascending sorted order:: > bstInorder :: BST a -> [a] > bstInorder EmptyBST = [] > bstInorder (Node val left right) = (bstInorder left) ++ [val] ++ (bstInorder right) For example:: > let t = bstInsert (bstInsert (bstInsert (bstInsert EmptyBST 5) 9) 2) 3 > bstInorder t [2,3,5,9] We can now implement a basic version of tree sort. Just insert all the elements of a list into a BST, and then extract them with ``bstInorder``:: > toBST :: Ord a => [a] -> BST a > toBST [] = EmptyBST > toBST (x:xs) = bstInsert (toBST xs) x > treesort :: Ord a => [a] -> [a] > treesort x = bstInorder (toBST x) For example:: > treesort [81, 79, 81, 82, 90, 77, 75] [75,77,79,81,81,82,90]