Skip to content

Commit 3c3707e

Browse files
authored
Chapter about data types (#10)
1 parent 3babc51 commit 3c3707e

File tree

1 file changed

+292
-2
lines changed

1 file changed

+292
-2
lines changed

src/intro/data-types.md

Lines changed: 292 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,299 @@
11
# Data Types
22

3-
## User Defined Types
3+
## Basics
44

5-
## Methods
5+
User can define custom data types using the `data` keyword. Fram supports
6+
so called *algebraic data types* (ADTs), where a type can be defined as a
7+
list of its constructors. In the simplest case, when constructors do not
8+
take any parameters, the data type definition is just an enumeration of
9+
the constructors. Names of types and their constructors must start with a
10+
capital letter.
11+
12+
```fram
13+
data Direction = North | South | East | West
14+
```
15+
16+
However, constructors can also take parameters. In this case, the
17+
constructor is followed by `of` keyword and a comma separated list of
18+
types of the parameters. The bar before the first constructor is optional.
19+
20+
```fram
21+
data Shape =
22+
| Arrow of Direction
23+
| Circle of Int
24+
| Rectangle of Int, Int
25+
```
26+
27+
Constructors of data types can be used as regular values. Constructors with
28+
parameters can be used as functions, and in particular, they can be partially
29+
applied.
30+
31+
```fram
32+
let westArrow = Arrow West
33+
let r13 = Rectangle 13
34+
```
35+
36+
Data types can be parametrized by other types.
37+
38+
```fram
39+
data OptPair X Y =
40+
| OnlyLeft of X
41+
| OnlyRight of Y
42+
| Both of X, Y
43+
```
44+
45+
## Pattern Matching
46+
47+
Elements of data types can be deconstructed and analyzed using pattern
48+
matching. Pattern matching is done using the `match ... with` construct,
49+
followed by the list of pattern matching clauses and the `end` keyword.
50+
Each clause consists of a pattern and an expression (body of the clause).
51+
The pattern can be built from constructors and binds variables that can
52+
be used in the body of the clause.
53+
54+
```fram
55+
let swapOptPair p =
56+
match p with
57+
| OnlyLeft x => OnlyRight x
58+
| OnlyRight y => OnlyLeft y
59+
| Both x y => Both y x
60+
end
61+
```
62+
63+
Fram supports deep pattern matching, i.e. matching on nested constructors.
64+
Additionally, the wildcard pattern `_` can be used to match any value without
65+
binding any variables. Because regular variables start with a lowercase letter,
66+
it is always clear when a pattern binds a variable or matches a constructor
67+
without parameters.
68+
69+
```fram
70+
let rotate shape =
71+
match shape with
72+
| Arrow North => Arrow East
73+
| Arrow South => Arrow West
74+
| Arrow East => Arrow South
75+
| Arrow West => Arrow North
76+
| Rectangle w h => Rectangle h w
77+
| Circle _ => shape
78+
end
79+
```
80+
81+
Patterns of different clauses within the same `match` construct may overlap.
82+
In this case, the first matching clause is used. Provided patterns must be
83+
*exhaustive*, i.e. each of possible values of the matched expression must be
84+
covered by at least one pattern.
85+
86+
Fram allows to use pattern in almost any place where a variable binder can be
87+
used. For example, patterns can be used in `let` bindings or as function
88+
parameters. Such patterns must obey the exhaustiveness condition.
89+
90+
```fram
91+
data Triple = Triple of Int, Int, Int
92+
93+
let sum1 (Triple x y z) =
94+
x + y + z
95+
96+
let sum2 t =
97+
let (Triple x y z) = t in
98+
x + y + z
99+
```
100+
101+
## Recursive Data Types
102+
103+
In Fram, data types can be recursive. This means that a data type can
104+
contain constructors that refer to the same data type. For example, a
105+
binary tree can be defined as follows.
106+
107+
```fram
108+
data Tree X =
109+
| Leaf
110+
| Node of Tree, X, Tree
111+
```
112+
113+
Note that recursive data types must be explicitly marked using the `rec`
114+
keyword. Mutually recursive data types can be defined using a `rec ... end`
115+
block. The same block can be shared with mutually recursive functions.
116+
117+
```fram
118+
rec
119+
data RoseTree X = Node of X, RoseTreeList X
120+
121+
data RoseTreeList X =
122+
| Nil
123+
| Cons of RoseTree X, RoseTreeList X
124+
125+
let map f (Node x ts) =
126+
Node (f x) (mapList f ts)
127+
128+
let mapList f ts =
129+
match ts with
130+
| Nil => Nil
131+
| Cons t ts => Cons (map f t) (mapList f ts)
132+
end
133+
end
134+
```
135+
136+
## Constructors with Named Parameters
137+
138+
Constructors can also have named parameters. This is useful when the meaning
139+
of the parameter is not clear from its type nor the position.
140+
141+
```fram
142+
data Color =
143+
| RGB of { red : Int, green : Int, blue : Int }
144+
| CMYK of { cyan : Int, magenta : Int, yellow : Int, black : Int }
145+
```
146+
147+
Named parameters of the constructor become part of its type scheme. Similarly
148+
to named parameters of functions, they must be always provided, but their order
149+
does not matter.
150+
151+
```fram
152+
let orange = RGB { red = 255, green = 127, blue = 0 }
153+
let black = CMYK { black = 100, cyan = 0, magenta = 0, yellow = 0 }
154+
```
155+
156+
Similarly, parameters of the data are attached to the constructor's scheme.
157+
This means that these parameters might be explicitly provided when the
158+
constructor is used.
159+
160+
```fram
161+
let emptyIntTree = Leaf {X=Int}
162+
```
163+
164+
To enforce type parameters to become anonymous, the `type` keyword can be
165+
used at the place of binding.
166+
167+
```fram
168+
data Box (type X) = Box of { value : X }
169+
```
170+
171+
Named parameters of the constructor can be used in pattern matching. The
172+
syntax is similar to the one used in explicit binding of named parameters
173+
of functions: after the name of the parameter, the `=` sign is used to
174+
provide the pattern for the parameter. For convenience, the `=` sign can be
175+
omitted if the pattern is a variable of the same name as the parameter.
176+
It is also possible to omit some of the named parameters.
177+
178+
```fram
179+
let unboxGetRed c =
180+
match c with
181+
| Box { value = RGB { red } } => red
182+
| _ => 0
183+
end
184+
```
6185

7186
## Records
8187

188+
In Fram, records are just syntactic sugar for data types with only one
189+
constructor which has only named parameters. To define a record, the
190+
name of the constructor and the `of` keyword are omitted.
191+
192+
```fram
193+
data Vec3D T =
194+
{ x : T
195+
, y : T
196+
, z : T
197+
}
198+
```
199+
200+
For record definitions, methods for accessing the fields are generated
201+
automatically. The above definition is equivalent to the following
202+
sequence of definitions.
203+
204+
```fram
205+
data Vec3D T = Vec3D of { x : T, y : T, z : T }
206+
207+
method x (Vec2D { x }) = x
208+
method y (Vec2D { y }) = y
209+
method z (Vec2D { z }) = z
210+
```
211+
212+
Therefore, records can be used in a similar way as records in other
213+
languages, but in fact, all operations on records are just combination of
214+
named parameters, methods, and constructors of ADTs.
215+
216+
```fram
217+
let cross (v1 : Vec3D Int) (v2 : Vec3D Int) =
218+
Vec3D
219+
{ x = v1.y * v2.z - v1.z * v2.y
220+
, y = v1.z * v2.x - v1.x * v2.z
221+
, z = v1.x * v2.y - v1.y * v2.x
222+
}
223+
```
224+
9225
## Existential Types
226+
227+
In Fram, each kind of named parameter can be a parameter of the constructor.
228+
In particular, constructors can have type parameters. Such parameters behave
229+
like existential types, i.e., their actual definition is not accessible when
230+
the data type is deconstructed. In the next chapter, we will see the most
231+
common use of existential types in Fram, but first let's start with a simple
232+
toy example. Here we define a Church encoding of an infinite stream, i.e., the
233+
stream is defined by its own unfold.
234+
235+
```fram
236+
data Stream X =
237+
Stream of {St, state : St, elem : St ->[] X, next : St ->[] St}
238+
```
239+
240+
The stream has a private state `state` of some type `St`, which can be
241+
different for each stream. The `elem` function computes the first element
242+
of the stream and the `next` function computes the next state of the tail of
243+
the stream. Note that the stream is not defined as a record. This is because
244+
the type `St` is not visible outside the constructor. In general, Fram forbids
245+
to use existential types in the record definition.
246+
247+
Existential types are just type parameters to the constructor, and as with
248+
other forms of type parameters, the user can provide them explicitly, or rely
249+
on the type inference.
250+
251+
```fram
252+
let natStream =
253+
Stream
254+
{ St = Int
255+
, state = 0
256+
, elem = fn n => n
257+
, next = fn n => n + 1
258+
}
259+
260+
let tail (Stream {state, elem, next}) =
261+
Stream { state = next state, elem, next }
262+
```
263+
264+
Name existential types can be explicitly bound in the pattern matching.
265+
266+
```fram
267+
let cons x (Stream {St, state, elem, next}) =
268+
Stream
269+
{ St = Option St
270+
, state = None
271+
, elem = fn s =>
272+
match s with
273+
| None => x
274+
| Some s => elem s
275+
end
276+
, next = fn s =>
277+
match s with
278+
| None => Some state
279+
| Some s => Some (next s)
280+
end
281+
}
282+
```
283+
284+
## Empty Data Types
285+
286+
Data types can have empty list of constructors.
287+
288+
```fram
289+
data Empty =
290+
```
291+
292+
Pattern matching on empty data types is possible, but the type of the
293+
matched expression must be known from the context.
294+
295+
```fram
296+
let ofEmpty (x : Empty) =
297+
match x with
298+
end
299+
```

0 commit comments

Comments
 (0)