Last weekend I was hacking Standard ML a bit.
There are excellent course on coursera called Programming Languages
Currently, the first half is available, that explains basic and advanced functional programming constructions with Standard ML as primary language. Later some idioms will be presented using Racket and Ruby (maybe, I will finally understand ruby) so you can treat this course as introduction to these languages. In fact, it teaches much more than “how to code in XXX”.
For playing with ML you need SML/NJ compiler and sml-mode for emacs.
If you don’t have emacs installed, do it right now!
Following snippets give you basic understanding of ML or refresh your knowledge for syntax.
Print “Hello, world!”:
fun print_hello_world () = print "Hello, world\n";
Variables (in fact, values):
val a = 38;
val b = a + 3;
val c = b + 1;
Conditionals:
val cond = if 5 > 4 then 5 else 4
Boolean operators have terrible names:
val cond2 = if 5 > 4 andalso 4 > 5 then 5 else 7
Function with one parameter:
fun square x = x * x
Function with more than one parameter:
fun sum (x, y, z) = x + y + z
Actually, it’s not three parameters, it’s one parameter - tuple:
fun swap (x, y) = (y, x)
To access tuple elements use hash-number, note you can specify tuple type:
fun get_month (date : int * string * int) : string= (#2 date)
val month1 = get_month(2012, "January", 21);
You can specify input types and return type, but ML is very clever. Almost always it can detect correct type using type inference without explicit definition.
I don’t understand it! Explain, please. Comments:
(* Operation '*' defined for int types, so this function has type int -> int *)
fun cube x = x * x * x
There is records. It’s like named tuples.
fun get_month_real ({year, month, day}) = month
val month2 = get_month_real({year=2012, month="January", day=21});
Using let
form we can define new lexical scope:
fun add3 x =
let
val num = 3
in
x + 3
end
This leads to closures (function + context):
fun add3 x =
let
val num = 3
fun add_to x = x + num
in
add_to x
end
Function add_to
capture num
value from context.
By the way functions are first-class objects and values also, so we can assign one function to another:
val another_cube = cube
Function composition becomes a piece of cake:
val cube_and_cube = cube o cube
We even can pass function as a parameter:
fun binary_apply (f, x, y) = f (x, y)
binary_apply(swap, 1, 2);
We don’t forced to provide function name, we can use lambdas:
binary_apply(fn (x, y) => x * y, 23, 46)
This helps us easily implement such useful functions as map
, reduce
and filter
.
But they are already available in
Standard ML Basis Library
Another great feature is pattern-matching. Here is factorial function
fun factorial x =
case x of
0 => 1
| _ => x * factorial(x - 1)
or list concatenation
fun concat (x, y) =
case x of
[] => y
| x::xs => x::concat(xs, y)
Honestly, it’s just baby examples of pattern matching. In fact, it is much more powerful.
And the last thing I wanted to show is datatypes:
To define a list:
datatype 'a list = EMPTY | CONS of 'a * 'a list
To create that list:
val list = CONS(1, CONS(2, CONS(3, EMPTY)))
That’s very small part of all that can be done in ML. There are also a lot of useful constructs and idioms that not covered: type synonyms, polymorphic types, equality types, tail recursion, mutual recursion, function wrapping, currying, partial application, advanced pattern matching, signatures, modules and lot more.
If you really interested you will find a lot of these terms exciting.
Full code is available on github
ML is a great language. Perhaps, it is not so great as Java in enterprise, and not so great as Clojure in rapid prototyping, but it’s worth to devote few days for ML basics. It really improve your dev skills and maybe made your Factories, Bridges and Proxies better.
mishadoff 16 February 2013