|
1 | 1 | # Data Types |
2 | 2 |
|
3 | | -## User Defined Types |
| 3 | +## Basics |
4 | 4 |
|
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 | +``` |
6 | 185 |
|
7 | 186 | ## Records |
8 | 187 |
|
| 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 | + |
9 | 225 | ## 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