data: product types
| Declare a type | |
| Several fields | |
| Deriving | |
| Types inside types |
Declare a type
A data declaration names a type and the constructor that builds its values. The constructor is a function, so it can be partially applied like any other:
data Point = Point Double Double
deriving (Show, Eq)
origin :: Point
origin = Point 0.0 0.0
The type name and the constructor name live in different namespaces, so reusing the word Point for both is legal and idiomatic. A value of this type carries two numbers and nothing else.
Several fields
A constructor with three arguments is a product of three types. The function that consumes it destructures all three at once:
data Segment = Segment Point Point
deriving (Show)
shiftX :: Double -> Point -> Point
shiftX d (Point x y) = Point (x + d) y
move :: Double -> Segment -> Segment
move d (Segment a b) = Segment (shiftX d a) (shiftX d b)
Nested patterns are the reason the type is useful: the shape of the value is written once and the compiler checks it.
Deriving
Three classes cover most needs, and the compiler writes all of them. Equality compares field by field, and the printed form mirrors the declaration:
data Point = Point Double Double
deriving (Show, Eq, Ord)
-- show (Point 1.0 2.0) == "Point 1.0 2.0"
-- Point 1.0 2.0 == Point 1.0 2.0 == True
Deriving Ord on a product compares the first field, then the second. That is a lexicographic order, which is occasionally useful and often surprising.
Types inside types
A field may be any type, including another type you declared or a container of one. This is how small models grow:
data Person = Person String Int
deriving (Show)
data Team = Team String [Person]
deriving (Show)
team = Team "core" [Person "Ada" 36, Person "Alan" 41]
Positional fields stop being readable at about three of them, which
is where records take over in lesson 26.
Next:
data: sum types
Article author: Arthur Isaev