Switching from OCaml to Standard ML
I decided to take a closer look at "the other ML", so I signed up for a course (the course is way more than just ML, you absolutely should enroll, but it's a different story).
Since they are conceptually similar, you can start writing in the other in several hours. For exact this reason those several hours will be very annoying though.
Even though SML is kinda less practical than OCaml (in sense of libraries and tools), I think it helped me to understand the design of both languages better.
There are multiple SML implementations, each of them not perfect in its own way.
Standard ML of New Jersey
Old, still popular. 32-bit only, for this reason absent from some distros that prefer something that can be built natively for multiple architectures. Intepreter only.
Includes both compiler and interpreter, works on 64-bit, but editor integration tools may not work with it out of the box.
Compiler only. Painfully slow. Said to be a whole program optimizing compiler, so probably is of greatest theoretical interest.
Subtle syntax differenced is what makes switching so annoying. Here's what I wish I have known (read: looked up in the docs) before starting.
Most standard library functions are not curried. For example, List.nth is α list * int → α rather than α list → int → α. Function arguments of higher-order functions also may not be curried, for example, the argument of List.foldl is α * β → β. In general, it looks like SML programmers tend to use tuples rather than using curried functions wherever possible.
There are several binding words: val for values; fun, an alias for val rec; and let for binding groups.
val x = 10 val incall = List.map (fn x => x + 1) fun fact x = case x of 0 => 1 | x => x * fact (x-1) val rec fact' = fn 0 => 1 | x => x * fact' (x - 1)
Nested bindings are very uncommon in SML because let/in pairs may contain multiple expressions.
let val x = 10 val y = x + 2 in x * y end
The word type is only for creating type aliases, while datatype is for creating new types.
Semicolon-separated expressions do not create a sequence in the imperative sense. You need parens.
fun hello () = (print "hello"; print " world\n")
Case expressions and match x with only differ in words and single vs. double arrows. The biggest difference is that you can use matching inside anonymous functions without any additional keywords.
val f = fn 0 => 1 | _ => 2
SML has first class constructors. Something like this will work fine:
map SOME [1,2,3] (* Gives [SOME 1, SOME 2, SOME 3] *)