adam@1500
|
1 (* Chapter 1: Introduction *)
|
adam@1494
|
2
|
adam@1497
|
3 (* begin hide *)
|
adam@1497
|
4 val show_string = mkShow (fn s => "\"" ^ s ^ "\"")
|
adam@1497
|
5 (* end *)
|
adam@1495
|
6
|
adam@1497
|
7 (* This tutorial by <a href="http://adam.chlipala.net/">Adam Chlipala</a> is licensed under a <a href="http://creativecommons.org/licenses/by-nc-nd/3.0/">Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License</a>. *)
|
adam@1493
|
8
|
adam@1497
|
9 (* This is a tutorial for the <a href="http://www.impredicative.com/ur/">Ur/Web</a> programming language. The <a href="http://www.impredicative.com/ur/">official project web site</a> is your starting point for information, like a reference manual and where to download the latest code release. In this tutorial, we'll just focus on introducing the language features. *)
|
adam@1497
|
10
|
adam@1497
|
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. *)
|
adam@1497
|
12
|
adam@1497
|
13 (* * Basics *)
|
adam@1497
|
14
|
adam@1501
|
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!) *)
|
adam@1493
|
16
|
adam@1493
|
17 (* begin eval *)
|
adam@1497
|
18 1 + 1
|
adam@1493
|
19 (* end *)
|
adam@1493
|
20
|
adam@1497
|
21 (* begin eval *)
|
adam@1497
|
22 1.2 + 3.4
|
adam@1497
|
23 (* end *)
|
adam@1496
|
24
|
adam@1497
|
25 (* begin eval *)
|
adam@1497
|
26 "Hello " ^ "world!"
|
adam@1497
|
27 (* end *)
|
adam@1497
|
28
|
adam@1497
|
29 (* begin eval *)
|
adam@1497
|
30 1 + 1 < 6
|
adam@1497
|
31 (* end *)
|
adam@1497
|
32
|
adam@1497
|
33 (* begin eval *)
|
adam@1497
|
34 0.0 < -3.2
|
adam@1497
|
35 (* end *)
|
adam@1497
|
36
|
adam@1497
|
37 (* begin eval *)
|
adam@1497
|
38 "Hello" = "Goodbye" || (1 * 2 <> 8 && True <> False)
|
adam@1497
|
39 (* end *)
|
adam@1497
|
40
|
adam@1497
|
41 (* We also have function definitions with type inference for parameter and return types. *)
|
adam@1497
|
42
|
adam@1497
|
43 fun double n = 2 * n
|
adam@1497
|
44
|
adam@1497
|
45 (* begin eval *)
|
adam@1497
|
46 double 8
|
adam@1497
|
47 (* end *)
|
adam@1497
|
48
|
adam@1497
|
49 fun fact n = if n = 0 then 1 else n * fact (n - 1)
|
adam@1497
|
50
|
adam@1497
|
51 (* begin eval *)
|
adam@1497
|
52 fact 5
|
adam@1497
|
53 (* end *)
|
adam@1497
|
54
|
adam@1497
|
55 (* Of course we have anonymous functions, too. *)
|
adam@1497
|
56
|
adam@1497
|
57 val inc = fn x => x + 1
|
adam@1497
|
58
|
adam@1497
|
59 (* begin eval *)
|
adam@1497
|
60 inc 3
|
adam@1497
|
61 (* end *)
|
adam@1497
|
62
|
adam@1497
|
63 (* Then there's parametric polymorphism. Unlike in ML and Haskell, polymorphic functions in Ur/Web often require full type annotations. That is because more advanced features (which we'll get to in the next chapter) make Ur type inference undecidable. *)
|
adam@1497
|
64
|
adam@1497
|
65 fun id [a] (x : a) : a = x
|
adam@1497
|
66
|
adam@1497
|
67 (* begin eval *)
|
adam@1497
|
68 id "hi"
|
adam@1497
|
69 (* end *)
|
adam@1497
|
70
|
adam@1497
|
71 fun compose [a] [b] [c] (f : b -> c) (g : a -> b) (x : a) : c = f (g x)
|
adam@1497
|
72
|
adam@1497
|
73 (* begin eval *)
|
adam@1497
|
74 compose inc inc 3
|
adam@1497
|
75 (* end *)
|
adam@1498
|
76
|
adam@1501
|
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. *)
|
adam@1498
|
78
|
adam@1498
|
79 fun predecessor (n : int) : option int = if n >= 1 then Some (n - 1) else None
|
adam@1498
|
80
|
adam@1498
|
81 (* begin hide *)
|
adam@1498
|
82 fun show_option [t] (_ : show t) : show (option t) =
|
adam@1498
|
83 mkShow (fn x => case x of
|
adam@1498
|
84 None => "None"
|
adam@1498
|
85 | Some x => "Some(" ^ show x ^ ")")
|
adam@1498
|
86 (* end *)
|
adam@1498
|
87
|
adam@1498
|
88 (* begin eval *)
|
adam@1498
|
89 predecessor 6
|
adam@1498
|
90 (* end *)
|
adam@1498
|
91
|
adam@1498
|
92 (* begin eval *)
|
adam@1498
|
93 predecessor 0
|
adam@1498
|
94 (* end *)
|
adam@1499
|
95
|
adam@1499
|
96 (* Naturally, there are lists, too! *)
|
adam@1499
|
97
|
adam@1499
|
98 val numbers : list int = 1 :: 2 :: 3 :: []
|
adam@1499
|
99 val strings : list string = "a" :: "bc" :: []
|
adam@1499
|
100
|
adam@1499
|
101 fun length [a] (ls : list a) : int =
|
adam@1499
|
102 case ls of
|
adam@1499
|
103 [] => 0
|
adam@1499
|
104 | _ :: ls' => 1 + length ls'
|
adam@1499
|
105
|
adam@1499
|
106 (* begin eval *)
|
adam@1499
|
107 length numbers
|
adam@1499
|
108 (* end *)
|
adam@1499
|
109
|
adam@1499
|
110 (* begin eval *)
|
adam@1499
|
111 length strings
|
adam@1499
|
112 (* end *)
|
adam@1500
|
113
|
adam@1501
|
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>. *)
|
adam@1500
|
115
|
adam@1500
|
116 (* begin hide *)
|
adam@1500
|
117 fun show_list [t] (_ : show t) : show (list t) =
|
adam@1500
|
118 mkShow (let
|
adam@1500
|
119 fun shower (ls : list t) =
|
adam@1500
|
120 case ls of
|
adam@1500
|
121 [] => "[]"
|
adam@1500
|
122 | x :: ls' => show x ^ " :: " ^ shower ls'
|
adam@1500
|
123 in
|
adam@1500
|
124 shower
|
adam@1500
|
125 end)
|
adam@1500
|
126 (* end *)
|
adam@1500
|
127
|
adam@1500
|
128 fun mp [a] [b] (f : a -> b) : list a -> list b =
|
adam@1500
|
129 let
|
adam@1500
|
130 fun loop (ls : list a) =
|
adam@1500
|
131 case ls of
|
adam@1500
|
132 [] => []
|
adam@1500
|
133 | x :: ls' => f x :: loop ls'
|
adam@1500
|
134 in
|
adam@1500
|
135 loop
|
adam@1500
|
136 end
|
adam@1500
|
137
|
adam@1500
|
138 (* begin eval *)
|
adam@1500
|
139 mp inc numbers
|
adam@1500
|
140 (* end *)
|
adam@1500
|
141
|
adam@1500
|
142 (* begin eval *)
|
adam@1500
|
143 mp (fn s => s ^ "!") strings
|
adam@1500
|
144 (* end *)
|
adam@1500
|
145
|
adam@1500
|
146 (* We can define our own polymorphic datatypes and write higher-order functions over them. *)
|
adam@1500
|
147
|
adam@1500
|
148 datatype tree a = Leaf of a | Node of tree a * tree a
|
adam@1500
|
149
|
adam@1500
|
150 (* begin hide *)
|
adam@1500
|
151 fun show_tree [t] (_ : show t) : show (tree t) =
|
adam@1500
|
152 mkShow (let
|
adam@1500
|
153 fun shower (t : tree t) =
|
adam@1500
|
154 case t of
|
adam@1500
|
155 Leaf x => "Leaf(" ^ show x ^ ")"
|
adam@1500
|
156 | Node (t1, t2) => "Node(" ^ shower t1 ^ ", " ^ shower t2 ^ ")"
|
adam@1500
|
157 in
|
adam@1500
|
158 shower
|
adam@1500
|
159 end)
|
adam@1500
|
160 (* end *)
|
adam@1500
|
161
|
adam@1500
|
162 fun size [a] (t : tree a) : int =
|
adam@1500
|
163 case t of
|
adam@1500
|
164 Leaf _ => 1
|
adam@1500
|
165 | Node (t1, t2) => size t1 + size t2
|
adam@1500
|
166
|
adam@1500
|
167 (* begin eval *)
|
adam@1500
|
168 size (Node (Leaf 0, Leaf 1))
|
adam@1500
|
169 (* end *)
|
adam@1500
|
170
|
adam@1500
|
171 (* begin eval *)
|
adam@1500
|
172 size (Node (Leaf 1.2, Node (Leaf 3.4, Leaf 4.5)))
|
adam@1500
|
173 (* end *)
|
adam@1500
|
174
|
adam@1500
|
175 fun tmap [a] [b] (f : a -> b) : tree a -> tree b =
|
adam@1500
|
176 let
|
adam@1500
|
177 fun loop (t : tree a) : tree b =
|
adam@1500
|
178 case t of
|
adam@1500
|
179 Leaf x => Leaf (f x)
|
adam@1500
|
180 | Node (t1, t2) => Node (loop t1, loop t2)
|
adam@1500
|
181 in
|
adam@1500
|
182 loop
|
adam@1500
|
183 end
|
adam@1500
|
184
|
adam@1500
|
185 (* begin eval *)
|
adam@1500
|
186 tmap inc (Node (Leaf 0, Leaf 1))
|
adam@1500
|
187 (* end *)
|
adam@1500
|
188
|
adam@1501
|
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>. *)
|
adam@1500
|
190
|
adam@1500
|
191 val x = { A = 0, B = 1.2, C = "hi", D = True }
|
adam@1500
|
192
|
adam@1500
|
193 (* begin eval *)
|
adam@1500
|
194 x.A
|
adam@1500
|
195 (* end *)
|
adam@1500
|
196
|
adam@1500
|
197 (* begin eval *)
|
adam@1500
|
198 x.C
|
adam@1500
|
199 (* end *)
|
adam@1500
|
200
|
adam@1500
|
201 type myRecord = { A : int, B : float, C : string, D : bool }
|
adam@1500
|
202
|
adam@1500
|
203 fun getA (r : myRecord) = r.A
|
adam@1500
|
204
|
adam@1500
|
205 (* begin eval *)
|
adam@1500
|
206 getA x
|
adam@1500
|
207 (* end *)
|
adam@1500
|
208
|
adam@1500
|
209 (* begin eval *)
|
adam@1500
|
210 getA (x -- #A ++ {A = 4})
|
adam@1500
|
211 (* end *)
|
adam@1500
|
212
|
adam@1500
|
213 val y = { A = "uhoh", B = 2.3, C = "bye", D = False }
|
adam@1500
|
214
|
adam@1500
|
215 (* begin eval *)
|
adam@1500
|
216 getA (y -- #A ++ {A = 5})
|
adam@1500
|
217 (* end *)
|
adam@1501
|
218
|
adam@1501
|
219
|
adam@1501
|
220 (* * Borrowed from ML *)
|
adam@1501
|
221
|
adam@1501
|
222 (* Ur includes an ML-style module system. The most basic use case involves packaging abstract types with their "methods." *)
|
adam@1501
|
223
|
adam@1501
|
224 signature COUNTER = sig
|
adam@1501
|
225 type t
|
adam@1501
|
226 val zero : t
|
adam@1501
|
227 val increment : t -> t
|
adam@1501
|
228 val toInt : t -> int
|
adam@1501
|
229 end
|
adam@1501
|
230
|
adam@1501
|
231 structure Counter : COUNTER = struct
|
adam@1501
|
232 type t = int
|
adam@1501
|
233 val zero = 9
|
adam@1501
|
234 val increment = plus 1
|
adam@1501
|
235 fun toInt x = x
|
adam@1501
|
236 end
|
adam@1501
|
237
|
adam@1501
|
238 (* begin eval *)
|
adam@1501
|
239 Counter.toInt (Counter.increment Counter.zero)
|
adam@1501
|
240 (* end *)
|
adam@1501
|
241
|
adam@1501
|
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. *)
|
adam@1501
|
243
|
adam@1501
|
244 signature STACK = sig
|
adam@1501
|
245 con t :: Type -> Type
|
adam@1501
|
246 val empty : a ::: Type -> t a
|
adam@1501
|
247 val push : a ::: Type -> t a -> a -> t a
|
adam@1501
|
248 val pop : a ::: Type -> t a -> option a
|
adam@1501
|
249 end
|
adam@1501
|
250
|
adam@1501
|
251 structure Stack : STACK = struct
|
adam@1501
|
252 con t = list
|
adam@1501
|
253 val empty [a] = []
|
adam@1501
|
254 fun push [a] (t : t a) (x : a) = x :: t
|
adam@1501
|
255 fun pop [a] (t : t a) = case t of
|
adam@1501
|
256 [] => None
|
adam@1501
|
257 | x :: _ => Some x
|
adam@1501
|
258 end
|
adam@1501
|
259
|
adam@1501
|
260 (* begin eval *)
|
adam@1501
|
261 Stack.pop (Stack.push (Stack.push Stack.empty "A") "B")
|
adam@1501
|
262 (* end *)
|
adam@1501
|
263
|
adam@1501
|
264 (* Ur also inherits the ML concept of <b>functors</b>, which are functions from modules to modules. *)
|
adam@1501
|
265
|
adam@1501
|
266 datatype order = Less | Equal | Greater
|
adam@1501
|
267
|
adam@1501
|
268 signature COMPARABLE = sig
|
adam@1501
|
269 type t
|
adam@1501
|
270 val compare : t -> t -> order
|
adam@1501
|
271 end
|
adam@1501
|
272
|
adam@1501
|
273 signature DICTIONARY = sig
|
adam@1501
|
274 type key
|
adam@1501
|
275 con t :: Type -> Type
|
adam@1501
|
276 val empty : a ::: Type -> t a
|
adam@1501
|
277 val insert : a ::: Type -> t a -> key -> a -> t a
|
adam@1501
|
278 val lookup : a ::: Type -> t a -> key -> option a
|
adam@1501
|
279 end
|
adam@1501
|
280
|
adam@1501
|
281 functor BinarySearchTree(M : COMPARABLE) : DICTIONARY where type key = M.t = struct
|
adam@1501
|
282 type key = M.t
|
adam@1501
|
283 datatype t a = Leaf | Node of t a * key * a * t a
|
adam@1501
|
284
|
adam@1501
|
285 val empty [a] = Leaf
|
adam@1501
|
286
|
adam@1501
|
287 fun insert [a] (t : t a) (k : key) (v : a) : t a =
|
adam@1501
|
288 case t of
|
adam@1501
|
289 Leaf => Node (Leaf, k, v, Leaf)
|
adam@1501
|
290 | Node (left, k', v', right) =>
|
adam@1501
|
291 case M.compare k' k of
|
adam@1501
|
292 Equal => Node (left, k, v, right)
|
adam@1501
|
293 | Less => Node (insert left k v, k', v', right)
|
adam@1501
|
294 | Greater => Node (left, k', v', insert right k v)
|
adam@1501
|
295
|
adam@1501
|
296 fun lookup [a] (t : t a) (k : key) : option a =
|
adam@1501
|
297 case t of
|
adam@1501
|
298 Leaf => None
|
adam@1501
|
299 | Node (left, k', v, right) =>
|
adam@1501
|
300 case M.compare k' k of
|
adam@1501
|
301 Equal => Some v
|
adam@1501
|
302 | Less => lookup left k
|
adam@1501
|
303 | Greater => lookup right k
|
adam@1501
|
304 end
|
adam@1501
|
305
|
adam@1501
|
306 structure IntTree = BinarySearchTree(struct
|
adam@1501
|
307 type t = int
|
adam@1501
|
308 fun compare n1 n2 =
|
adam@1501
|
309 if n1 = n2 then
|
adam@1501
|
310 Equal
|
adam@1501
|
311 else if n1 < n2 then
|
adam@1501
|
312 Less
|
adam@1501
|
313 else
|
adam@1501
|
314 Greater
|
adam@1501
|
315 end)
|
adam@1501
|
316
|
adam@1501
|
317 (* begin eval *)
|
adam@1501
|
318 IntTree.lookup (IntTree.insert (IntTree.insert IntTree.empty 0 "A") 1 "B") 1
|
adam@1501
|
319 (* end *)
|
adam@1501
|
320
|
adam@1501
|
321 (* It is sometimes handy to rebind modules to shorter names. *)
|
adam@1501
|
322
|
adam@1501
|
323 structure IT = IntTree
|
adam@1501
|
324
|
adam@1501
|
325 (* begin eval *)
|
adam@1501
|
326 IT.lookup (IT.insert (IT.insert IT.empty 0 "A") 1 "B") 0
|
adam@1501
|
327 (* end *)
|
adam@1501
|
328
|
adam@1501
|
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. *)
|
adam@1501
|
330
|
adam@1501
|
331 open IT
|
adam@1501
|
332
|
adam@1501
|
333 (* begin eval *)
|
adam@1501
|
334 lookup (insert (insert empty 0 "A") 1 "B") 2
|
adam@1501
|
335 (* end *)
|