Gripe #1 is that so much of the ecosystem is dead. Poly/ML and MLton have Fedora packages, and (legacy) SML/NJ builds easily, and is only 'legacy' because there's a new one actively built on LLVM. But Moscow ML meanwhile can't build under modern C compilers; even test C programs in its config scripts are bitrotten. Manticore doesn't compile. mosml.org points to the repo of a guy who's ignored it for five years (with five years of Advent of Code repos - in Haskell), although he's maintained https://try.mosml.org/ recently. successor-ml.org is an idea without DNS resolution.
A consequence of the decay: the Hamlet reference interpreter for SuccML, which is not abandoned, written in SML, and has seven build targets for seven different SML implementations, still doesn't compile on modern MLton because MLton's CLI interface has changed since the build target was added. It also doesn't build with mosml even though mosml hasn't changed in five years.
The promise of a standard is that your code will still compile in the future, but if software still bitrots, I'd rather have opam and multiple switches.
Living SMLs: mlton, MaPLe (MPL), Mlkit
More petty gripes follow.
SML books
SML books have strangely bad style, like the writer doesn't really embrace the language, or expects the reader not to:
fun double (x:int):int = 2 * x;
fun square (x:int):int = x * x;
fun power (x:int,y:int):int = if (y=0) then 1 else x * power (x,y-1);
vs.
fun double x = 2 * x;
fun square x = x * x;
fun power x y =
if y = 0 then 1 else x * power x (y - 1);
Why type annotations? Because C has them. Why parentheses around the test? Because C has that. Why tupled arguments when SML funs match even more naturally on multiple arguments than OCaml does, and when you lose currying? Because it's more visually consistent with unit arguments and constructors/operators in arguments (x-1), and more consistent with anonymous functions which aren't as expressive - and because it looks more like C. An emphasis on consistency might also drive type annotations as signatures must have them, but as inference is used where possible later in this book,
The semicolons also resemble C, but that's not a matter of style as it's unavoidable.
REPL
SML/NJ prints reals as ints when possible, so it prints an invalid definition of it.
- 2.0;
val it = 2 : real
- val it = 2 : int;
val it = 2 : int
- val it = 2 : real;
stdIn:11.5-11.18 Error: expression does not match constraint [overload - bad instantiation]
expression: 'Z[INT]
constraint: real
in expression:
2: real
Unicode
SML/NJ can read and print non-ASCII by default but represents it with escape codes. MLton needs a flag; Poly/ML refuses altogether.
- print "русский\n"; (* smlnj *)
русский
- "русский";
val it = "\209\128\209\131\209\129\209\129\208\186\208\184\208\185" : string
> "русский"; (* poly/ml *)
poly: : error: unprintable character \209 found in string
poly: : error: unprintable character \128 found in string
poly: : error: unprintable character \209 found in string
poly: : error: unprintable character \131 found in string
poly: : error: unprintable character \209 found in string
poly: : error: unprintable character \129 found in string
poly: : error: unprintable character \209 found in string
poly: : error: unprintable character \129 found in string
poly: : error: unprintable character \208 found in string
poly: : error: unprintable character \186 found in string
poly: : error: unprintable character \208 found in string
poly: : error: unprintable character \184 found in string
poly: : error: unprintable character \208 found in string
poly: : error: unprintable character \185 found in string
Static Errors