comparison doc/intro.ur @ 1501:841b08995259

Module system tutorial
author Adam Chlipala <adam@chlipala.net>
date Sat, 16 Jul 2011 11:48:11 -0400
parents 483cc0602565
children 2f9b7382dd1d
comparison
equal deleted inserted replaced
1500:483cc0602565 1501:841b08995259
10 10
11 (* Ur/Web contains a web-indendent core language called Ur, which will be the subject of the first few chapters of the tutorial. Ur inherits its foundation from ML and Haskell, then going further to add fancier stuff. This first chapter of the tutorial reviews the key ML and Haskell features, giving their syntax in Ur. *) 11 (* Ur/Web contains a web-indendent core language called Ur, which will be the subject of the first few chapters of the tutorial. Ur inherits its foundation from ML and Haskell, then going further to add fancier stuff. This first chapter of the tutorial reviews the key ML and Haskell features, giving their syntax in Ur. *)
12 12
13 (* * Basics *) 13 (* * Basics *)
14 14
15 (* Let's start with features shared with both ML and Haskell. First, we have the basic numeric, string, and Boolean stuff. (In the following examples, "==" is used to indicate the result of evaluating an expression. It's not valid Ur syntax!) *) 15 (* Let's start with features shared with both ML and Haskell. First, we have the basic numeric, string, and Boolean stuff. (In the following examples, <tt>==</tt> is used to indicate the result of evaluating an expression. It's not valid Ur syntax!) *)
16 16
17 (* begin eval *) 17 (* begin eval *)
18 1 + 1 18 1 + 1
19 (* end *) 19 (* end *)
20 20
72 72
73 (* begin eval *) 73 (* begin eval *)
74 compose inc inc 3 74 compose inc inc 3
75 (* end *) 75 (* end *)
76 76
77 (* The "option" type family is like ML's "option" or Haskell's "maybe." We also have a "case" expression form lifted directly from ML. Note that, while Ur follows most syntactic conventions of ML, one key difference is that type families appear before their arguments, as in Haskell. *) 77 (* The <tt>option</tt> type family is like ML's <tt>option</tt> or Haskell's <tt>Maybe</tt>. We also have a <tt>case</tt> expression form lifted directly from ML. Note that, while Ur follows most syntactic conventions of ML, one key difference is that type families appear before their arguments, as in Haskell. *)
78 78
79 fun predecessor (n : int) : option int = if n >= 1 then Some (n - 1) else None 79 fun predecessor (n : int) : option int = if n >= 1 then Some (n - 1) else None
80 80
81 (* begin hide *) 81 (* begin hide *)
82 fun show_option [t] (_ : show t) : show (option t) = 82 fun show_option [t] (_ : show t) : show (option t) =
109 109
110 (* begin eval *) 110 (* begin eval *)
111 length strings 111 length strings
112 (* end *) 112 (* end *)
113 113
114 (* And lists make a good setting for demonstrating higher-order functions and local functions. (This example also introduces one idiosyncrasy of Ur, which is that "map" is a keyword, so we name our "map" function "mp.") *) 114 (* And lists make a good setting for demonstrating higher-order functions and local functions. (This example also introduces one idiosyncrasy of Ur, which is that <tt>map</tt> is a keyword, so we name our"map" function <tt>mp</tt>. *)
115 115
116 (* begin hide *) 116 (* begin hide *)
117 fun show_list [t] (_ : show t) : show (list t) = 117 fun show_list [t] (_ : show t) : show (list t) =
118 mkShow (let 118 mkShow (let
119 fun shower (ls : list t) = 119 fun shower (ls : list t) =
184 184
185 (* begin eval *) 185 (* begin eval *)
186 tmap inc (Node (Leaf 0, Leaf 1)) 186 tmap inc (Node (Leaf 0, Leaf 1))
187 (* end *) 187 (* end *)
188 188
189 (* We also have anonymous record types, as in Standard ML. The next chapter will show that there is quite a lot more going on here with records than in SML or OCaml, but we'll stick to the basics in this chapter. We will add one tantalizing hint of what's to come by demonstrating the record concatention operator "++" and the record field removal operator "--". *) 189 (* We also have anonymous record types, as in Standard ML. The next chapter will show that there is quite a lot more going on here with records than in SML or OCaml, but we'll stick to the basics in this chapter. We will add one tantalizing hint of what's to come by demonstrating the record concatention operator <tt>++</tt> and the record field removal operator <tt>--</tt>. *)
190 190
191 val x = { A = 0, B = 1.2, C = "hi", D = True } 191 val x = { A = 0, B = 1.2, C = "hi", D = True }
192 192
193 (* begin eval *) 193 (* begin eval *)
194 x.A 194 x.A
213 val y = { A = "uhoh", B = 2.3, C = "bye", D = False } 213 val y = { A = "uhoh", B = 2.3, C = "bye", D = False }
214 214
215 (* begin eval *) 215 (* begin eval *)
216 getA (y -- #A ++ {A = 5}) 216 getA (y -- #A ++ {A = 5})
217 (* end *) 217 (* end *)
218
219
220 (* * Borrowed from ML *)
221
222 (* Ur includes an ML-style module system. The most basic use case involves packaging abstract types with their "methods." *)
223
224 signature COUNTER = sig
225 type t
226 val zero : t
227 val increment : t -> t
228 val toInt : t -> int
229 end
230
231 structure Counter : COUNTER = struct
232 type t = int
233 val zero = 9
234 val increment = plus 1
235 fun toInt x = x
236 end
237
238 (* begin eval *)
239 Counter.toInt (Counter.increment Counter.zero)
240 (* end *)
241
242 (* We may package not just abstract types, but also abstract type families. Here we see our first use of the <tt>con</tt> keyword, which stands for <b>constructor</b>. Constructors are a generalization of types to include other "compile-time things"; for instance, type families, which are assigned the kind <tt>Type -> Type</tt>. Kinds are to constructors as types are to normal values. We also see how to write the type of a polymorphic function, using the <tt>:::</tt> syntax for type variable binding. This <tt>:::</tt> differs from the <tt>::</tt> used with the <tt>con</tt> keyword because it marks a type parameter as implicit, so that it need not be supplied explicitly at call sites. Such an option is the only one available in ML and Haskell, but, in the next chapter, we'll meet cases where it is appropriate to use explicit constructor parameters. *)
243
244 signature STACK = sig
245 con t :: Type -> Type
246 val empty : a ::: Type -> t a
247 val push : a ::: Type -> t a -> a -> t a
248 val pop : a ::: Type -> t a -> option a
249 end
250
251 structure Stack : STACK = struct
252 con t = list
253 val empty [a] = []
254 fun push [a] (t : t a) (x : a) = x :: t
255 fun pop [a] (t : t a) = case t of
256 [] => None
257 | x :: _ => Some x
258 end
259
260 (* begin eval *)
261 Stack.pop (Stack.push (Stack.push Stack.empty "A") "B")
262 (* end *)
263
264 (* Ur also inherits the ML concept of <b>functors</b>, which are functions from modules to modules. *)
265
266 datatype order = Less | Equal | Greater
267
268 signature COMPARABLE = sig
269 type t
270 val compare : t -> t -> order
271 end
272
273 signature DICTIONARY = sig
274 type key
275 con t :: Type -> Type
276 val empty : a ::: Type -> t a
277 val insert : a ::: Type -> t a -> key -> a -> t a
278 val lookup : a ::: Type -> t a -> key -> option a
279 end
280
281 functor BinarySearchTree(M : COMPARABLE) : DICTIONARY where type key = M.t = struct
282 type key = M.t
283 datatype t a = Leaf | Node of t a * key * a * t a
284
285 val empty [a] = Leaf
286
287 fun insert [a] (t : t a) (k : key) (v : a) : t a =
288 case t of
289 Leaf => Node (Leaf, k, v, Leaf)
290 | Node (left, k', v', right) =>
291 case M.compare k' k of
292 Equal => Node (left, k, v, right)
293 | Less => Node (insert left k v, k', v', right)
294 | Greater => Node (left, k', v', insert right k v)
295
296 fun lookup [a] (t : t a) (k : key) : option a =
297 case t of
298 Leaf => None
299 | Node (left, k', v, right) =>
300 case M.compare k' k of
301 Equal => Some v
302 | Less => lookup left k
303 | Greater => lookup right k
304 end
305
306 structure IntTree = BinarySearchTree(struct
307 type t = int
308 fun compare n1 n2 =
309 if n1 = n2 then
310 Equal
311 else if n1 < n2 then
312 Less
313 else
314 Greater
315 end)
316
317 (* begin eval *)
318 IntTree.lookup (IntTree.insert (IntTree.insert IntTree.empty 0 "A") 1 "B") 1
319 (* end *)
320
321 (* It is sometimes handy to rebind modules to shorter names. *)
322
323 structure IT = IntTree
324
325 (* begin eval *)
326 IT.lookup (IT.insert (IT.insert IT.empty 0 "A") 1 "B") 0
327 (* end *)
328
329 (* One can even use the <tt>open</tt> command to import a module's namespace wholesale, though this can make it harder for someone reading code to tell which identifiers come from which modules. *)
330
331 open IT
332
333 (* begin eval *)
334 lookup (insert (insert empty 0 "A") 1 "B") 2
335 (* end *)