0% found this document useful (0 votes)
6 views

4-1-CustomType

The document covers custom types in programming languages, focusing on defining new types, data constructors, and type classes. It explains how to create and export custom types, use record syntax, and implement recursive data types. Additionally, it discusses functors and provides examples of their implementation in Haskell.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views

4-1-CustomType

The document covers custom types in programming languages, focusing on defining new types, data constructors, and type classes. It explains how to create and export custom types, use record syntax, and implement recursive data types. Additionally, it discusses functors and provides examples of their implementation in Haskell.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 32

CMPT383 Comparative Programming Languages

Lecture 4-1: Custom Types

Yuepeng Wang
Spring 2025

1
Overview
• Custom data types

• Type synonyms

• Recursive data types

• Custom type classes

• Functors

• Kinds

2
Defining New Types
• We can define our own (data) types using the data keyword

• Example: the Bool type in the standard library


data Bool = False | True

• The part before the equal sign denotes the type

• The parts after the equal sign are data constructors (or value constructors)

• The | symbol is read as or

• Meaning: the Bool type can have a value of False or True

3
Data Constructors
• Data constructors can take parameters (fields) and produce a value
data Shape = Circle Float Float Float
| Rectangle Float Float Float Float

• Meaning: the Shape type can have circle and rectangle values
• Each circle has three parameters (x, y, radius)
• Each rectangle has four parameters (x, y, length, width)

• Data constructors are functions


ghci> :t Circle
Circle :: Float -> Float -> Float -> Shape
ghci> :t Rectangle
Rectangle :: Float -> Float -> Float -> Float -> Shape
4
Data Constructors
• Data constructors can be pattern matched

• Example: area of a shape


area :: Shape -> Float
area (Circle _ _ radius) = pi * radius * radius
area (Rectangle _ _ length width) = length * width

• Execution
ghci> area (Circle 0 0 1)
3.1415927
ghci> area $ Rectangle 0 0 20 10
200.0

5
Defining Types using Custom Types
• We can use custom types in the definition of a new type
data Point = Point Float Float
data Shape = Circle Point Float
| Rectangle Point Float Float

• Note that we can use the same name for a type and a data constructor

• Common for types with only one data constructor

• Example: create a circle at the origin of the coordinate


originCircle :: Float -> Shape
originCircle radius = Circle (Point 0 0) radius

6
Exporting Custom Types
• We can export custom types and data constructors in a module
module Shapes
( Point(..)
, Shape(..)
, originCircle
) where

• Export all data constructors: use ..

• Export selected data constructors: provide a comma-separated list

• Export no data constructors: just provide the type name

• We can still create an instance of the type using other functions


7
Record Syntax
• If a data constructor has several fields and it’s not obvious which field is
which, we can use the record syntax
data Car = Car { make :: String
, model :: String
, year :: Int }

• Can create values using the record syntax

• Fields are functions


ghci> :t model
model :: Car -> String
ghci> let c = Car {make="Ford", model="Focus", year=2022}
ghci> model c
"Focus"

8
Type Constructors
• Type constructors can take types as parameters and produce a new type
data Maybe a = Nothing
| Just a

• Here, a is a type parameter


• Maybe is a type constructor (not a type)
• It can produce a type of Maybe Int, Maybe Shape, etc
• No value can simply have type “Maybe”
• Nothing is a value (a data constructor with no parameters) of type Maybe a.
Its type is polymorphic
• Just is a data constructor that has type a -> Maybe a

9
Type Constructors
• A nullary type constructor (zero parameters) is a type

• What other type constructors have we learned?

• Data.Map.Map takes two type parameters: key type and value type

• [ ] takes a type parameter: element type. [a] is a syntactic sugar of [ ] a

• A type is said to be concrete if

• it doesn’t take any type parameters at all (e.g., Int, Bool), or

• it takes type parameters and they’re are instantiated (e.g., Maybe Int)
10
Example: Vector
• 3-dimensional vector where all fields have the same type

data Vector a = Vector a a a

• Implement a vplus function for vector addition

vplus :: Num a => Vector a -> Vector a -> Vector a


vplus (Vector x1 y1 z1) (Vector x2 y2 z2)
= Vector (x1 + x2) (y1 + y2) (z1 + z2)

11
Example: elemIndex
• The elemIndex function in Data.List returns the index of the first element
in the given list which is equal to the query element, or Nothing if there is
no such element

• What is the type of elemIndex?

• One possible implementation


elemIndex :: Eq a => a -> [a] -> Maybe Int
elemIndex v xs = elemIndexAux 0 v xs
where elemIndexAux :: Eq a => Int -> a -> [a] -> Maybe Int
elemIndexAux _ _ [] = Nothing
elemIndexAux i v (x:xs)
| v == x = Just i
| otherwise = elemIndexAux (i + 1) v xs
12
Example: Either
• Either is a type constructor that has two type parameters

data Either a b = Left a | Right b

• If Left is used, its contents have type a

• If Right is used, its contents have type b

• Either is more general than Maybe


• Maybe can represent the results of computations that could fail
• In addition, Either can distinguish different failures

13
Deriving Type Classes
ghci> Vector 'a' 'b' 'c'
<interactive>: ... error:
• No instance for (Show (Vector Char)) ...

• The error is because "Vector a" is not an instance of the Show type class

• We can easily make a custom type an instance of some type classes


• Eq, Ord, Show, Read, Enum, Bounded
• Enum is for things that have predecessors and successors
• Bounded is for things that have minBound and maxBound

• To fix the Show problem


data Vector a = Vector a a a deriving (Show)

14
Example: Day
• A Day type that represents the days of a week
data Day = Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday
deriving (Eq, Ord, Show, Read, Enum, Bounded)

• The functions from the Eq, Ord, Show, Read, Enum, and Bounded type
classes are available to use
ghci> read "Monday" :: Day
Monday
ghci> succ Monday
Tuesday
ghci> maxBound :: Day
Sunday
ghci> [Tuesday .. Friday]
[Tuesday,Wednesday,Thursday,Friday]

15
Type Synonyms
• We can create synonyms for types using the type keyword
• Synonyms and original types are interchangeable

type String = [Char]

• We can parametrize type synonyms

type AssocList k v = [(k, v)]

• We can partially apply type parameters and get new type constructors
type IntMap v = Map Int v
type IntMap = Map Int

16
Recursive Data Types
• A data constructor can have several fields and each field must be
of some type (can even be the type to be defined)
• We get a recursive data type if the type has itself as a type in the fields
data List a = Empty | Cons a (List a) deriving (Eq, Ord, Show, Read)

• Record syntax
data List a = Empty | Cons { listHead :: a, listTail :: List a }
deriving (Eq, Ord, Show, Read)

• Example: list concatenation


listConcat :: List a -> List a -> List a
listConcat Empty ys = ys
listConcat (Cons x xs) ys = Cons x (listConcat xs ys)

17
Recursive Data Types
• A binary tree is an empty tree or a node with a value and two binary trees
data Tree a = EmptyTree
| Node a (Tree a) (Tree a)
deriving (Show)

• A binary search tree (with unique keys) is a binary tree where each node
stores a key greater than all keys in the left subtree but less than all keys
in the right subtree
• Example: write a function to check if a key is in a binary search tree
treeElem :: Ord a => a -> Tree a -> Bool
treeElem k EmptyTree = False
treeElem k (Node x left right)
| k == x = True
| k < x = treeElem k left
| k > x = treeElem k right
18
Type Classes
• A type class defines some function, similar to an interface

• If a type T is an instance of a type class, we can use the functions that the
type class defines with T
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x == y = not (x /= y)
x /= y = not (x == y)

• The type declarations for functions are required


• The default function implementations are optional
• Need to override at least one function for termination

19
Instance of a Type Class
• We can make a custom type an instance of a type class by hand
• Example: traffic light
data TrafficLight = Red | Yellow | Green

• Make TrafficLight an instance of Eq


instance Eq TrafficLight where
Red == Red = True
Yellow == Yellow = True
Green == Green = True
_ == _ = False

• A minimal set of functions to implement/override is called a minimal


complete definition for the type class (e.g., == for Eq)
• If no default implementations, the minimal complete definition has both funcs

20
Instance of a Type Class
• Make TrafficLight an instance of the Show type class
instance Show TrafficLight where
show Red = "Red light"
show Yellow = "Yellow light"
show Green = "Green light"

• Execution
ghci> Green
Green light

21
Subclasses
• We can make type classes that are subclasses of other type classes

• Syntax: class Superclass => Subclass where

• Example
class Eq a => Ord a where
(<=) :: a -> a -> Bool
(<) :: a -> a -> Bool
...
x < y = x <= y && x /= y

• Subclassing is a class constraint on a class declaration

• Functions in subclass can assume existence of functions in superclass


22
Parameterized Types as Instances
• We can make a parameterized type an instance of a type class

• Example: Maybe a
instance Eq a => Eq (Maybe a) where
Just x == Just y = x == y
Nothing == Nothing = True
_ == _ = False

• Note we cannot make “Maybe” an instance because Maybe is not a type

• The class constraint is added because we need == on x and y

23
Example: YesNo
• Some weakly typed languages (e.g., JavaScript) treat an “empty” value as
False and other values as True for conditions in if
• Haskell doesn’t work like that, but we can simulate it by a YesNo type
class
class YesNo a where
yesno :: a -> Bool

• For Int instance YesNo Int where


yesno 0 = False
yesno _ = True

• For [a] instance YesNo [a] where


yesno [] = False
yesno _ = True

24
Example: YesNo
• For Bool
instance YesNo Bool where
yesno = id
id :: a -> a is the identity function

• For Maybe a
instance YesNo (Maybe a) where
yesno Nothing = False
yesno _ = True

• The “if” function


yesnoIf :: YesNo a => a -> b -> b -> b
yesnoIf cond yesRes noRes = if yesno cond then yesRes else noRes

25
Functors
• The Functor type class is for things that can be mapped over
class Functor f where
fmap :: (a -> b) -> f a -> f b

• map applies a function to every element in a list

• Intuitively, fmap is a generalization of map. It applies a function to every


element inside some structure (functor) without changing the structure itself

• A functor in Haskell is an instance of the Functor type class that obeys the
following laws
• fmap id == id
• fmap (g . h) == fmap g . fmap h
26
List as a Functor
• [ ] (List) is a functor
instance Functor [] where
-- fmap :: (a -> b) -> [a] -> [b]
fmap = map

• map id == id

• map (g . h) == map g . map h


ghci> fmap (+1) [1, 2, 3]
[2,3,4]

27
Maybe as a Functor
• Maybe is a functor
instance Functor Maybe where
-- fmap :: (a -> b) -> Maybe a -> Maybe b
fmap f (Just x) = Just (f x)
fmap _ Nothing = Nothing

• fmap id (Just x) == Just (id x) == Just x == id (Just x)


• fmap id Nothing == Nothing == id Nothing
• fmap (g . h) (Just x) == Just ((g . h) x) == Just (g (h x))
== fmap g (Just (h x)) == fmap g (fmap h (Just x)) == (fmap g . fmap h) (Just x)
• fmap (g . h) Nothing = Nothing == fmap g Nothing == (fmap g . fmap h) Nothing
ghci> fmap (+1) (Just 10)
Just 11
ghci> fmap (+1) Nothing
Nothing

28
Tree as a Functor
• Tree is also a functor
instance Functor Tree where
-- fmap :: (a -> b) -> Tree a -> Tree b
fmap f (Node x left right) = Node (f x) (fmap f left) (fmap f right)
fmap _ EmptyTree = EmptyTree

• We can check that both functor laws hold

• Execution example
ghci> fmap (+1) (Node 2
(Node 1 EmptyTree EmptyTree)
(Node 3 EmptyTree EmptyTree))
Node 3 (Node 2 EmptyTree EmptyTree) (Node 4 EmptyTree EmptyTree)
ghci> fmap (+1) EmptyTree
EmptyTree

29
Either a as a Functor
• Either a b has two type parameters, but we can partially apply the type
constructor to obtain “Either a”, which is also a functor!
instance Functor (Either a) where
-- fmap :: (b -> c) -> Either a b -> Either a c
fmap f (Right x) = Right (f x)
fmap _ (Left x) = Left x

• Both functor laws hold

• Execution example
ghci> fmap (+1) (Right 10)
Right 11
ghci> fmap (+1) (Left "error")
Left "error"

30
Example: inc
• Define a general increment function on functors
• What is its type?
inc :: (Functor f, Num a) => f a -> f a
inc = fmap (+1)

• Execution example
ghci> inc [1, 2, 3]
[2,3,4]
ghci> inc (Just 10)
Just 11
ghci> inc (Node 1 EmptyTree (Node 2 EmptyTree EmptyTree))
Node 2 EmptyTree (Node 3 EmptyTree EmptyTree)
ghci> inc (Right 10)
Right 11

31
Kinds
• Functions take parameters to produce values. Type constructors take
parameters to produce types (eventually)
• Type is an attribute of data. Kind is an attribute of type (“type” of type)
ghci> :k Int
Int :: *
ghci> :k Maybe
Maybe :: * -> *
ghci> :k Either
Either :: * -> * -> *

• Here, * denotes a type


• The kind of all functors must be * -> *

32

You might also like