Standard ML is a functional programming language with type inference and some side-effects. Some of the hard parts of learning Standard ML are: Recursion, pattern matching, type inference (guessing the right types but never allowing implicit type conversion). Standard ML is distinguished from Haskell by including references, allowing variables to be updated.
1(* Comments in Standard ML begin with (* and end with *). Comments can be
2 nested which means that all (* tags must end with a *) tag. This comment,
3 for example, contains two nested comments. *)
4
5(* A Standard ML program consists of declarations, e.g. value declarations: *)
6val rent = 1200
7val phone_no = 5551337
8val pi = 3.14159
9val negative_number = ~15 (* Yeah, unary minus uses the 'tilde' symbol *)
10
11(* Optionally, you can explicitly declare types. This is not necessary as
12 ML will automatically figure out the types of your values. *)
13val diameter = 7926 : int
14val e = 2.718 : real
15val name = "Bobby" : string
16
17(* And just as importantly, functions: *)
18fun is_large(x : int) = if x > 37 then true else false
19
20(* Floating-point numbers are called "reals". *)
21val tau = 2.0 * pi (* You can multiply two reals *)
22val twice_rent = 2 * rent (* You can multiply two ints *)
23(* val meh = 1.25 * 10 *) (* But you can't multiply an int and a real *)
24val yeh = 1.25 * (Real.fromInt 10) (* ...unless you explicitly convert
25 one or the other *)
26
27(* +, - and * are overloaded so they work for both int and real. *)
28(* The same cannot be said for division which has separate operators: *)
29val real_division = 14.0 / 4.0 (* gives 3.5 *)
30val int_division = 14 div 4 (* gives 3, rounding down *)
31val int_remainder = 14 mod 4 (* gives 2, since 3*4 = 12 *)
32
33(* ~ is actually sometimes a function (e.g. when put in front of variables) *)
34val negative_rent = ~(rent) (* Would also have worked if rent were a "real" *)
35
36(* There are also booleans and boolean operators *)
37val got_milk = true
38val got_bread = false
39val has_breakfast = got_milk andalso got_bread (* 'andalso' is the operator *)
40val has_something = got_milk orelse got_bread (* 'orelse' is the operator *)
41val is_sad = not(has_something) (* not is a function *)
42
43(* Many values can be compared using equality operators: = and <> *)
44val pays_same_rent = (rent = 1300) (* false *)
45val is_wrong_phone_no = (phone_no <> 5551337) (* false *)
46
47(* The operator <> is what most other languages call !=. *)
48(* 'andalso' and 'orelse' are called && and || in many other languages. *)
49
50(* Actually, most of the parentheses above are unnecessary. Here are some
51 different ways to say some of the things mentioned above: *)
52fun is_large x = x > 37 (* The parens above were necessary because of ': int' *)
53val is_sad = not has_something
54val pays_same_rent = rent = 1300 (* Looks confusing, but works *)
55val is_wrong_phone_no = phone_no <> 5551337
56val negative_rent = ~rent (* ~ rent (notice the space) would also work *)
57
58(* Parentheses are mostly necessary when grouping things: *)
59val some_answer = is_large (5 + 5) (* Without parens, this would break! *)
60(* val some_answer = is_large 5 + 5 *) (* Read as: (is_large 5) + 5. Bad! *)
61
62
63(* Besides booleans, ints and reals, Standard ML also has chars and strings: *)
64val foo = "Hello, World!\n" (* The \n is the escape sequence for linebreaks *)
65val one_letter = #"a" (* That funky syntax is just one character, a *)
66
67val combined = "Hello " ^ "there, " ^ "fellow!\n" (* Concatenate strings *)
68
69val _ = print foo (* You can print things. We are not interested in the *)
70val _ = print combined (* result of this computation, so we throw it away. *)
71(* val _ = print one_letter *) (* Only strings can be printed this way *)
72
73
74val bar = [ #"H", #"e", #"l", #"l", #"o" ] (* SML also has lists! *)
75(* val _ = print bar *) (* Lists are unfortunately not the same as strings *)
76
77(* Fortunately they can be converted. String is a library and implode and size
78 are functions available in that library that take strings as argument. *)
79val bob = String.implode bar (* gives "Hello" *)
80val bob_char_count = String.size bob (* gives 5 *)
81val _ = print (bob ^ "\n") (* For good measure, add a linebreak *)
82
83(* You can have lists of any kind *)
84val numbers = [1, 3, 3, 7, 229, 230, 248] (* : int list *)
85val names = [ "Fred", "Jane", "Alice" ] (* : string list *)
86
87(* Even lists of lists of things *)
88val groups = [ [ "Alice", "Bob" ],
89 [ "Huey", "Dewey", "Louie" ],
90 [ "Bonnie", "Clyde" ] ] (* : string list list *)
91
92val number_count = List.length numbers (* gives 7 *)
93
94(* You can put single values in front of lists of the same kind using
95 the :: operator, called "the cons operator" (known from Lisp). *)
96val more_numbers = 13 :: numbers (* gives [13, 1, 3, 3, 7, ...] *)
97val more_groups = ["Batman","Superman"] :: groups
98
99(* Lists of the same kind can be appended using the @ ("append") operator *)
100val guest_list = [ "Mom", "Dad" ] @ [ "Aunt", "Uncle" ]
101
102(* This could have been done with the "cons" operator. It is tricky because the
103 left-hand-side must be an element whereas the right-hand-side must be a list
104 of those elements. *)
105val guest_list = "Mom" :: "Dad" :: [ "Aunt", "Uncle" ]
106val guest_list = "Mom" :: ("Dad" :: ("Aunt" :: ("Uncle" :: [])))
107
108(* If you have many lists of the same kind, you can concatenate them all *)
109val everyone = List.concat groups (* [ "Alice", "Bob", "Huey", ... ] *)
110
111(* A list can contain any (finite) number of values *)
112val lots = [ 5, 5, 5, 6, 4, 5, 6, 5, 4, 5, 7, 3 ] (* still just an int list *)
113
114(* Lists can only contain one kind of thing... *)
115(* val bad_list = [ 1, "Hello", 3.14159 ] : ??? list *)
116
117
118(* Tuples, on the other hand, can contain a fixed number of different things *)
119val person1 = ("Simon", 28, 3.14159) (* : string * int * real *)
120
121(* You can even have tuples inside lists and lists inside tuples *)
122val likes = [ ("Alice", "ice cream"),
123 ("Bob", "hot dogs"),
124 ("Bob", "Alice") ] (* : (string * string) list *)
125
126val mixup = [ ("Alice", 39),
127 ("Bob", 37),
128 ("Eve", 41) ] (* : (string * int) list *)
129
130val good_bad_stuff =
131 (["ice cream", "hot dogs", "chocolate"],
132 ["liver", "paying the rent" ]) (* : string list * string list *)
133
134
135(* Records are tuples with named slots *)
136
137val rgb = { r=0.23, g=0.56, b=0.91 } (* : {b:real, g:real, r:real} *)
138
139(* You don't need to declare their slots ahead of time. Records with
140 different slot names are considered different types, even if their
141 slot value types match up. For instance... *)
142
143val Hsl = { H=310.3, s=0.51, l=0.23 } (* : {H:real, l:real, s:real} *)
144val Hsv = { H=310.3, s=0.51, v=0.23 } (* : {H:real, s:real, v:real} *)
145
146(* ...trying to evaluate `Hsv = Hsl` or `rgb = Hsl` would give a type
147 error. While they're all three-slot records composed only of `real`s,
148 they each have different names for at least some slots. *)
149
150(* You can use hash notation to get values out of tuples. *)
151
152val H = #H Hsv (* : real *)
153val s = #s Hsl (* : real *)
154
155(* Functions! *)
156fun add_them (a, b) = a + b (* A simple function that adds two numbers *)
157val test_it = add_them (3, 4) (* gives 7 *)
158
159(* Larger functions are usually broken into several lines for readability *)
160fun thermometer temp =
161 if temp < 37
162 then "Cold"
163 else if temp > 37
164 then "Warm"
165 else "Normal"
166
167val test_thermo = thermometer 40 (* gives "Warm" *)
168
169(* if-sentences are actually expressions and not statements/declarations.
170 A function body can only contain one expression. There are some tricks
171 for making a function do more than just one thing, though. *)
172
173(* A function can call itself as part of its result (recursion!) *)
174fun fibonacci n =
175 if n = 0 then 0 else (* Base case *)
176 if n = 1 then 1 else (* Base case *)
177 fibonacci (n - 1) + fibonacci (n - 2) (* Recursive case *)
178
179(* Sometimes recursion is best understood by evaluating a function by hand:
180
181 fibonacci 4
182 ~> fibonacci (4 - 1) + fibonacci (4 - 2)
183 ~> fibonacci 3 + fibonacci 2
184 ~> (fibonacci (3 - 1) + fibonacci (3 - 2)) + fibonacci 2
185 ~> (fibonacci 2 + fibonacci 1) + fibonacci 2
186 ~> ((fibonacci (2 - 1) + fibonacci (2 - 2)) + fibonacci 1) + fibonacci 2
187 ~> ((fibonacci 1 + fibonacci 0) + fibonacci 1) + fibonacci 2
188 ~> ((1 + fibonacci 0) + fibonacci 1) + fibonacci 2
189 ~> ((1 + 0) + fibonacci 1) + fibonacci 2
190 ~> (1 + fibonacci 1) + fibonacci 2
191 ~> (1 + 1) + fibonacci 2
192 ~> 2 + fibonacci 2
193 ~> 2 + (fibonacci (2 - 1) + fibonacci (2 - 2))
194 ~> 2 + (fibonacci (2 - 1) + fibonacci (2 - 2))
195 ~> 2 + (fibonacci 1 + fibonacci 0)
196 ~> 2 + (1 + fibonacci 0)
197 ~> 2 + (1 + 0)
198 ~> 2 + 1
199 ~> 3 which is the 4th Fibonacci number, according to this definition
200
201 *)
202
203(* A function cannot change the variables it can refer to. It can only
204 temporarily shadow them with new variables that have the same names. In this
205 sense, variables are really constants and only behave like variables when
206 dealing with recursion. For this reason, variables are also called value
207 bindings. An example of this: *)
208
209val x = 42
210fun answer(question) =
211 if question = "What is the meaning of life, the universe and everything?"
212 then x
213 else raise Fail "I'm an exception. Also, I don't know what the answer is."
214val x = 43
215val hmm = answer "What is the meaning of life, the universe and everything?"
216(* Now, hmm has the value 42. This is because the function answer refers to
217 the copy of x that was visible before its own function definition. *)
218
219
220(* Functions can take several arguments by taking one tuples as argument: *)
221fun solve2 (a : real, b : real, c : real) =
222 ((~b + Math.sqrt(b * b - 4.0 * a * c)) / (2.0 * a),
223 (~b - Math.sqrt(b * b - 4.0 * a * c)) / (2.0 * a))
224
225(* Sometimes, the same computation is carried out several times. It makes sense
226 to save and re-use the result the first time. We can use "let-bindings": *)
227fun solve2 (a : real, b : real, c : real) =
228 let val discr = b * b - 4.0 * a * c
229 val sqr = Math.sqrt discr
230 val denom = 2.0 * a
231 in ((~b + sqr) / denom,
232 (~b - sqr) / denom)
233 end
234
235
236(* Pattern matching is a funky part of functional programming. It is an
237 alternative to if-sentences. The fibonacci function can be rewritten: *)
238fun fibonacci 0 = 0 (* Base case *)
239 | fibonacci 1 = 1 (* Base case *)
240 | fibonacci n = fibonacci (n - 1) + fibonacci (n - 2) (* Recursive case *)
241
242(* Pattern matching is also possible on composite types like tuples, lists and
243 records. Writing "fun solve2 (a, b, c) = ..." is in fact a pattern match on
244 the one three-tuple solve2 takes as argument. Similarly, but less intuitively,
245 you can match on a list consisting of elements in it (from the beginning of
246 the list only). *)
247fun first_elem (x::xs) = x
248fun second_elem (x::y::xs) = y
249fun evenly_positioned_elems (odd::even::xs) = even::evenly_positioned_elems xs
250 | evenly_positioned_elems [odd] = [] (* Base case: throw away *)
251 | evenly_positioned_elems [] = [] (* Base case *)
252
253(* The case expression can also be used to pattern match and return a value *)
254datatype temp =
255 C of real
256 | F of real
257
258(* Declaring a new C temp value...
259 val t: temp = C 45.0 *)
260
261fun temp_to_f t =
262 case t of
263 C x => x * (9.0 / 5.0) + 32.0
264 | F x => x
265
266(* When matching on records, you must use their slot names, and you must bind
267 every slot in a record. The order of the slots doesn't matter though. *)
268
269fun rgbToTup {r, g, b} = (r, g, b) (* fn : {b:'a, g:'b, r:'c} -> 'c * 'b * 'a *)
270fun mixRgbToTup {g, b, r} = (r, g, b) (* fn : {b:'a, g:'b, r:'c} -> 'c * 'b * 'a *)
271
272(* If called with {r=0.1, g=0.2, b=0.3}, either of the above functions
273 would return (0.1, 0.2, 0.3). But it would be a type error to call them
274 with {r=0.1, g=0.2, b=0.3, a=0.4} *)
275
276(* Higher order functions: Functions can take other functions as arguments.
277 Functions are just other kinds of values, and functions don't need names
278 to exist. Functions without names are called "anonymous functions" or
279 lambda expressions or closures (since they also have a lexical scope). *)
280val is_large = (fn x => x > 37)
281val add_them = fn (a,b) => a + b
282val thermometer =
283 fn temp => if temp < 37
284 then "Cold"
285 else if temp > 37
286 then "Warm"
287 else "Normal"
288
289(* The following uses an anonymous function directly and gives "ColdWarm" *)
290val some_result = (fn x => thermometer (x - 5) ^ thermometer (x + 5)) 37
291
292(* Here is a higher-order function that works on lists (a list combinator) *)
293(* map f l
294 applies f to each element of l from left to right,
295 returning the list of results. *)
296val readings = [ 34, 39, 37, 38, 35, 36, 37, 37, 37 ] (* first an int list *)
297val opinions = List.map thermometer readings (* gives [ "Cold", "Warm", ... ] *)
298
299(* And here is another one for filtering lists *)
300val warm_readings = List.filter is_large readings (* gives [39, 38] *)
301
302(* You can create your own higher-order functions, too. Functions can also take
303 several arguments by "currying" them. Syntax-wise this means adding spaces
304 between function arguments instead of commas and surrounding parentheses. *)
305fun map f [] = []
306 | map f (x::xs) = f(x) :: map f xs
307
308(* map has type ('a -> 'b) -> 'a list -> 'b list and is called polymorphic. *)
309(* 'a is called a type variable. *)
310
311
312(* We can declare functions as infix *)
313val plus = add_them (* plus is now equal to the same function as add_them *)
314infix plus (* plus is now an infix operator *)
315val seven = 2 plus 5 (* seven is now bound to 7 *)
316
317(* Functions can also be made infix before they are declared *)
318infix minus
319fun x minus y = x - y (* It becomes a little hard to see what's the argument *)
320val four = 8 minus 4 (* four is now bound to 4 *)
321
322(* An infix function/operator can be made prefix with 'op' *)
323val n = op + (5, 5) (* n is now 10 *)
324
325(* 'op' is useful when combined with high order functions because they expect
326 functions and not operators as arguments. Most operators are really just
327 infix functions. *)
328(* foldl f init [x1, x2, ..., xn]
329 returns
330 f(xn, ...f(x2, f(x1, init))...)
331 or init if the list is empty. *)
332val sum_of_numbers = foldl op+ 0 [1, 2, 3, 4, 5]
333
334
335(* Datatypes are useful for creating both simple and complex structures *)
336datatype color = Red | Green | Blue
337
338(* Here is a function that takes one of these as argument *)
339fun say(col) =
340 if col = Red then "You are red!" else
341 if col = Green then "You are green!" else
342 if col = Blue then "You are blue!" else
343 raise Fail "Unknown color"
344
345val _ = print (say(Red) ^ "\n")
346
347(* Datatypes are very often used in combination with pattern matching *)
348fun say Red = "You are red!"
349 | say Green = "You are green!"
350 | say Blue = "You are blue!"
351
352(* We did not include the match arm `say _ = raise Fail "Unknown color"`
353because after specifying all three colors, the pattern is exhaustive
354and redundancy is not permitted in pattern matching *)
355
356
357(* Here is a binary tree datatype *)
358datatype 'a btree = Leaf of 'a
359 | Node of 'a btree * 'a * 'a btree (* three-arg constructor *)
360
361(* Here is a binary tree *)
362val myTree = Node (Leaf 9, 8, Node (Leaf 3, 5, Leaf 7))
363
364(* Drawing it, it might look something like...
365
366 8
367 / \
368 leaf -> 9 5
369 / \
370 leaf -> 3 7 <- leaf
371 *)
372
373(* This function counts the sum of all the elements in a tree *)
374fun count (Leaf n) = n
375 | count (Node (leftTree, n, rightTree)) = count leftTree + n + count rightTree
376
377val myTreeCount = count myTree (* myTreeCount is now bound to 32 *)
378
379
380(* Exceptions! *)
381(* Exceptions can be raised/thrown using the reserved word 'raise' *)
382fun calculate_interest(n) = if n < 0.0
383 then raise Domain
384 else n * 1.04
385
386(* Exceptions can be caught using "handle" *)
387val balance = calculate_interest ~180.0
388 handle Domain => ~180.0 (* balance now has the value ~180.0 *)
389
390(* Some exceptions carry extra information with them *)
391(* Here are some examples of built-in exceptions *)
392fun failing_function [] = raise Empty (* used for empty lists *)
393 | failing_function [x] = raise Fail "This list is too short!"
394 | failing_function [x,y] = raise Overflow (* used for arithmetic *)
395 | failing_function xs = raise Fail "This list is too long!"
396
397(* We can pattern match in 'handle' to make sure
398 a specific exception was raised, or grab the message *)
399val err_msg = failing_function [1,2] handle Fail _ => "Fail was raised"
400 | Domain => "Domain was raised"
401 | Empty => "Empty was raised"
402 | _ => "Unknown exception"
403
404(* err_msg now has the value "Unknown exception" because Overflow isn't
405 listed as one of the patterns -- thus, the catch-all pattern _ is used. *)
406
407(* We can define our own exceptions like this *)
408exception MyException
409exception MyExceptionWithMessage of string
410exception SyntaxError of string * (int * int)
411
412(* File I/O! *)
413(* Write a nice poem to a file *)
414fun writePoem(filename) =
415 let val file = TextIO.openOut(filename)
416 val _ = TextIO.output(file, "Roses are red,\nViolets are blue.\n")
417 val _ = TextIO.output(file, "I have a gun.\nGet in the van.\n")
418 in TextIO.closeOut(file)
419 end
420
421(* Read a nice poem from a file into a list of strings *)
422fun readPoem(filename) =
423 let val file = TextIO.openIn filename
424 val poem = TextIO.inputAll file
425 val _ = TextIO.closeIn file
426 in String.tokens (fn c => c = #"\n") poem
427 end
428
429val _ = writePoem "roses.txt"
430val test_poem = readPoem "roses.txt" (* gives [ "Roses are red,",
431 "Violets are blue.",
432 "I have a gun.",
433 "Get in the van." ] *)
434
435(* We can create references to data which can be updated *)
436val counter = ref 0 (* Produce a reference with the ref function *)
437
438(* Assign to a reference with the assignment operator *)
439fun set_five reference = reference := 5
440
441(* Read a reference with the dereference operator *)
442fun equals_five reference = !reference = 5
443
444(* We can use while loops for when recursion is messy *)
445fun decrement_to_zero r = if !r < 0
446 then r := 0
447 else while !r >= 0 do r := !r - 1
448
449(* This returns the unit value (in practical terms, nothing, a 0-tuple) *)
450
451(* To allow returning a value, we can use the semicolon to sequence evaluations *)
452fun decrement_ret x y = (x := !x - 1; y)
Further learning ¶
- Install an interactive compiler (REPL), for example Poly/ML, Moscow ML, SML/NJ.
- Follow the Coursera course Programming Languages.
- Read ML for the Working Programmer by Larry C. Paulson.
- Use StackOverflow’s sml tag.
- Solve exercises on Exercism.io’s Standard ML track.