http://adam.chlipala.net/mlcomp/ Comparing Objective Caml and Standard ML This page compares point-by-point the Objective Caml (OCaml) and Standard ML (SML) programming languages, the two main representatives of the ML programming language family. The comparison includes language design and current tool availability, as well as further social factors. This page isn't meant to be a complete comparison, but rather to highlight differences that might lead to choosing one language over the other. For many of the points below, there is a clear distinction between the languages as to which is more "practical" in that design decision and which is more mathematically pure, concerned with formal properties of the language, or otherwise research-oriented. In such cases, these icons appear: [hardha] ...next to the more "practical" language [scienti] ...next to the more "pure" language Objective Caml Standard ML Syntax See this syntax comparison for more details. Array/string shorthands Special syntactic sugar is defined for array and string accesses. These operations receive no special treatment. let arr = [| 1; 2; 3 |];; val arr = Array.fromList [1, 2, 3]; let two = arr.(1);; val two = Array.sub (arr, 1); arr.(2) <- 6;; Array.update (arr, 2, 6); let str = "Hello";; val str = "Hello"; let e = str.[1];; val e = String.sub (str, 1); More syntactic sugar clutters the language definition. Arrays and strings are central data structures of "practical Arrays and strings show up infrequently in traditional [hardha] programming," so they should be as usable as we can make [scienti] functional programming applications, and many new ML them. programmers accustomed to array-based work could quite profitably switch to datatype-based solutions instead. Character literals Uses 'c' Uses #"c" Apostrophes mean "type variable" or "prime" in SML and are parts of identifiers; they shouldn't be confused with character literals. Many symbolically-oriented SML [hardha] OCaml's syntax is shorter and follows the "standard" set by [scienti] programs don't manipulate individual characters, so we C. shouldn't complicate the lexer to support related features. (Consider 'a', which could be either a type variable or a character literal, depending on where it appers.) Identifier conventions Module, module type, and constructor names must start in capital No capitalization convention letters and other identifiers can't type myType = datatype myType = A a | B;; | b; let f = function val f = fn A' -> 0 (* A' is signaled as an unbound constructor. *) a' => 0 (* a' is processed as a variable. *) | B -> 1;; | b => 1; (* This case is signaled as redundant. *) This convention stops a very nasty class of pattern matching bugs involving confusion between variables and variant constructors. It More flexibility can't hurt if you're careful, right? In also eases the tasks of text editor syntax highlighters, making it actuality, most SML programmers with opinions would prefer the easy to distinguish between module and variable names by color, for OCaml convention. example. Let-binding syntax Separate top-level let and let expressions Syntactic class of declarations and let..in..end construct for binding them val six = 6 let six = 6 fun fact x = if x = 0 then 1 else x * fact (x - 1) let rec fact x = if x = 0 then 1 else x * fact (x - 1) val six_fact = let six_fact = let let six = 6 in val six = 6 let rec fact x = if x = 0 then 1 else x * fact (x - 1) in fun fact x = if x = 0 then 1 else x * fact (x - 1) fact 6 in fact 6 end In practice, this approach leads to some very confusing error Having a unified mechanism for top-level and local bindings leads messages, since the compiler is less able to predict what grouping to less duplication of functionality, and let..in..end seems you really intended. empirically to lead to clearer error messages. Overloaded "minus" The standard - symbol is used for both negation and subtraction. Tilde (~) is used for negation. 1 - 2;; 1 - 2; 1 + -2;; 1 + ~2; Differentiating subtraction and negation upholds the SML position that operators are just identifiers like any [hardha] Lots of programmers would be confused by throwing over this [scienti] others that happen to be used via special syntax. Modulo long-standing convention. the overloading of binary arithmetic operators, SML avoids situations where an identifier means different things in different contexts. Semicolon precedence Semicolon binds more tightly than match bodies and anonymous Semicolon binds less tightly than case bodies and anonymous functions functions match x with case x of 0 -> print_endline "It's zero!"; 0 => (print "It's zero!\n"; true true) | _ -> print_endline "It's not!"; | _ => (print "It's not!\n"; false;; false); fun s -> print_string s; s;; fn s => (print s; s); begin match x with case x of 0 -> print_endline "It's zero!" 0 => print "It's zero!\n" | _ -> print_endline "It's not!" | _ => print "It's not!\n"; end; print "The End\n"; print_endline "The End";; [hardha] The OCaml precedence rules favor imperative code, expecting The SML precedence rules favor pure functional code, semicolons to be used as sequencers in many places. [scienti] requiring parentheses around many places where semicolons might be used. User-defined infix operators Fixed precedence rules for infix operators; curried arguments User-defined precedence rules for infix operators; tupled arguments let (++) x y = x + y + y;; fun op++ (x, y) = x + y + y; infix 6 ++; 1 ++ 2;; 1 ++ 2; List.map ((++) 1) [1; 2; 3] map (fn x => 1 ++ x) [1, 2, 3] There is never any doubt on seeing an OCaml program about how to User-specified precedences make it easier to implement parse an expression that includes infix operators. However, sometimes nicer-looking embedded languages. However, infix declarations were the inflexibility of precedences forces use of "extra" parentheses. never integrated well with the module system, meaning that client Curried operators make it easy to use partial applications as code can't import fixities from a library module. arguments to higher-order functions. Operators Arithmetic operators Different operators for arithmetic over different built-in numerical Overloaded operators that handle several built-in numerical types types let four_int = 2 + 2 val four_int = 2 + 2 let four_float = 2.0 +. 2.0 val four_float = 2.0 + 2.0 Apparently following a "principled overloading or none at SML overloads the arithmetic operators in an ad-hoc way. [scienti] all" philosophy, OCaml breaks with a convention found in [hardha] This has some unfortunate interactions with type most other languages. inference, like inferring by default that a variable used in arithmetic has type int. Equality Equality works on any type but may raise run-time exceptions. Equality types characterize valid equality arguments. let member (x : 'a) (ls : 'a list) : bool = fun member (x : ''a) (ls : ''a list) : bool = List.exists ((=) x) ls;; List.exists (fn y => x = y) ls; member 2 [1; 2; 3];; member 2 [1, 2, 3]; member false [true; false; true];; member false [true, false, true]; member (fun () -> ()) [fun () -> ()];; (* Exception *) member (fn () => ()) [fn () => ()]; (* Type error *) With SML, it's clear at compile time that no equality It's very convenient to be able to compare values for operation will fail with a run-time type error, and the equality without having to track whether or not they're types of functions that use equality clearly state that [hardha] comparable, and the lack of equality types simplifies the [scienti] fact, removing a need for informal documentation. SML language. However, run-time type errors on bad equality equality types are often criticized as a special case of comparisons are no fun. type classes that ought to be replaced with that more general mechanism. Standard libraries Arrays vs. vectors A single array type family Distinguishes between (imperative) arrays and (functional) vectors Isolating impure behavior as much as possible is very [hardha] Programmers used to C and its ilk don't want to have to helpful at making programs easy to understand. A worry about whether or not their arrays are functional. [scienti] function taking a vector as input is guaranteed not to "change" it, and the function's type broadcasts that fact. Currying vs. tupling All standard library functions are curried. Higher-order functions are usually curried, with tupling the default elsewhere. List.iter (output_string ch) ["Hello"; " there!"];; app (fn s => TextIO.output (ch, s)) ["Hello", " there!"]; List.iter (fun (ch, s) -> output_string ch s) [(ch1, "A"); (ch2, "B")];; app TextIO.output [(ch1, "A"), (ch2, "B")]; Currying makes it easy to pass partial applications as arguments to Tupling makes it easy to treat entire function argument lists as higher-order functions. first-class values. Exceptions vs. option types Most standard library functions indicate conditions like "end of Most standard library functions indicate conditions like "end of file" by throwing exceptions. file" by returning NONE. Using exceptions provides uniformity with other uses of exceptions to Giving multiple-return-status functions option return types makes signal more traditional "error" conditions. This choice also makes it their potential behavior clear, without requiring the programmer easier to use multiple-return-status functions in situations where to consult informal documentation to learn that. you know that they will encounter the common case. Generic functions Standard library contains generic functions that couldn't be Equality is the only generic function of this kind, and it's built implemented in OCaml, like comparison and hashing. into the language. 1 < 2;; 1 < 2; [1; 2; 3] < [4; 5; 6];; [1, 2, 3] < [4, 5, 6]; (* Type error *) (fun () -> ()) < (fun () -> ());; (* Exception *) (fn () => ()) < (fn () => ());; (* Type error *) It's very convenient to implement container structures and [hardha] other functionality without having to worry about passing [scienti] SML avoids features like this whose formalization would around comparison or hashing functions. Some of the generic require considerable extra work. operations, like comparison, can raise run-time type errors. Mutability of strings Strings are mutable. Strings are immutable (and in fact the string type is defined as a synonym for a vector type). One often performs purely functional string manipulations, and it's useful for program understanding to have types that reflect Mutating strings can be convenient. that. If you want mutable "strings", use the CharArray module. By defining strings as vectors, we avoid including another primitive base type that goes unused in many programs, like OCaml does. Data types Algebraic datatype constructors Second-class constructors that duplicate some tuple functionality First-class constructors List.map (fun x -> Some x) [1; 2; 3];; map Some [1, 2, 3]; type ('a, 'b) pair = Pair of 'a * 'b;; datatype ('a, 'b) pair = Pair of 'a * 'b; match e with case e of Pair p -> p (* Type error *) Pair p => p This scheme is easier to compile efficiently in the absence of dataflow analysis. However, by not treating variant constructors as SML avoids the two problems mentioned for OCaml. Using dataflow functions, OCaml forces the use of wrappers as arguments to analysis, SML compilers like MLton compile uses of higher-order functions. By not treating multiple arguments to multiple-argument constructors efficiently. constructors as tuples, OCaml creates feature overlap between variants and tuples, making it hard to convert between them. Format strings printf- and scanf-style format strings are built into the language. No format strings Printf.printf "%s, %d\n" "Hello" 1234; It would go against the SML philosophy to include such a [hardha] Format strings have proved to be a useful enough idiom that complicated type system feature that most parts of most special language support is justified. [scienti] programs wouldn't use. It also turns out that SML is already sufficient to implement something almost identical to printf. Labeled function arguments Has them Doesn't have them let name ~firstName ~lastName = firstName ^ " " ^ lastName;; fun name {firstName, lastName} = firstName ^ " " ^ lastName; name ~lastName:"Doe" ~firstName:"John";; name {lastName = "Doe", firstName = "John"}; Labeled arguments complicate the language definition, [hardha] Labeled arguments remove the need to keep track of which and their benefits can often be attained through other arguments go in which positions. [scienti] means. OCaml's lack of anonymous record types seems to explain much of the rationale for including labeled arguments. Mutable fields Record fields may be marked mutable. No concept of mutable fields type mut_pair = {mutable x : int; mutable y : int};; type mut_pair = {x : int ref, y : int ref}; let myPair = {x = 1; y = 2}; val myPair = {x = ref 1, y = ref 2}; myPair.x <- 3;; #x myPair := 3; ref types are a simpler feature and can be used to Mutable fields make imperative programming more convenient, implement a work-alike to mutable fields. With dataflow [hardha] and they have a more natural efficient compilation strategy [scienti] analysis, SML compilers like MLton produce efficient in the absence of dataflow analysis. binaries from code that uses refs to implement mutable fields. Optional function arguments Has them Doesn't have them let printIt ?(prefix = "Hello, ") s = print_endline (prefix ^ s);; fun printIt (prefix, s) = print (Option.getOpt (prefix, "Hello, ") ^ s ^ "\n"); printIt "world";; printIt ~prefix:"1" "2";; printIt (NONE, "world"); printIt (SOME "1", "2"); [hardha] Optional arguments make it easy to have highly-configurable Optional arguments complicate the language definition, functions that can be called succinctly in the common case. [scienti] and their uses tend in practice to be implementable in other, not much more verbose ways. Polymorphic variants Has them Doesn't have them let getNum = function datatype ('a, 'b) base = `Num n -> Some n Num of 'a | _ -> None;; | Rest of 'b; type t1 = [`Num of int | `Other of string];; type t1 = (int, string) base; getNum (`Num 6 : t1);; getNum (Num 6 : t1); type t2 = [`Num of float | `Something of bool];; type t2 = (real, bool) base; getNum (`Num 6.0 : t2);; getNum (Num 6.0 : t2); Polymorphic variants can lead to some quite confusing error messages, and static checking is a less effective bug-finder in a program that uses them. Newcomers to [hardha] Polymorphic variants enable greater degrees of genericity [scienti] OCaml often fall into using polymorphic variants by than regular variants do. default, since they have a lower cost of entry than regular variants, even though most ML programmers agree that regular variants are more desirable when applicable. Records Declared, generative record types where field names can shadow others Anonymous record types type coord = {x : int; y : int};; let addCoord c = c.x + c.y;; fun addCoord (c : {x : int, y : int}) = #x c + #y c; type coord' = {x : int; y : int};; type unrelated = {x : real, y : bool, z : string}; addCoord {x = 1; y = 2};; (* Type error *) val myCoord = {x = 1, y = 2}; type unrelated = {x : float; y : bool; z : string};; let myCoord = {x = 1; y = 2};; (* Type error *) By looking at just a single field of a valid record construction expression, the expression's type is uniquely determined, which makes In general, anonymous record types are a lightweight feature that type inference easier compared to anonymous record types. However, avoids the problems mentioned for OCaml's records. On the other namespace management of fields can be arduous. To use a record type hand, type inference for anonymous record types can be tricky, declared in another module, one must either open that module or often prompting SML programmers to include type annotations on reference a field with a full path that includes the module name. record arguments to functions or wrap record types inside It's also easy to unintentionally shadow a field name with a new single-constructor datatypes. record type declaration. Recursive types Any mutually recursive type definitions are allowed, as long as a All recursion goes through algebraic datatypes. cycle-detection algorithm accepts them. type 'a tree = {data : 'a; children : 'a tree list};; datatype 'a tree = Tree of {data : 'a, children : 'a tree list}; type 'a btree = datatype 'a btree = Leaf of 'a Leaf of 'a | Node of 'a forests * 'a forests | Node of 'a forests * 'a forests and 'a forest = 'a btree list withtype 'a forest = 'a btree list and 'a forests = 'a forest list;; and 'a forests = 'a btree list list; (* Definition of forest must be substituted *) OCaml features a single mutually-recursive type syntax, By forcing all type recursion to go through datatype [hardha] overloaded to cover synonyms, record types, and variants. [scienti] declarations, SML simplifies its formal semantics to Most reasonable definitions just work. only have to deal with recursive types "in one place," without sacrificing any expressivity. Pattern matching Guards Has them Doesn't have them fun f x = fn SOME y => let f x = function if y < x then Some y when y < x -> y y | _ -> 0;; else 0 | _ => 0; Let's not clutter up the language definition, eh? You [hardha] These can be a significant code-space saver. [scienti] can always define a local function that you call in the several cases to which you must compile a single guard use. "Or" patterns Has them Doesn't have them let f = function val f = fn 0 | 1 -> true 0 => true | _ -> false;; | 1 => true | _ => false; Let's not clutter up the language definition, eh? You [hardha] These can be a significant code-space saver. [scienti] can always define a local function that you call in all the branches you would have lumped together with an "or" pattern. (An SML/NJ extension allows "or" patterns.) Modules and classes First-class functors and signatures Allows functors that return functors or take them as arguments, Doesn't allow these functors and signatures in modules, etc. module F (A : sig end) (B : sig end) = struct end;; functor F (A : sig end) (B : sig end) = struct end; (* SML/NJ only *) module M = struct module type S = sig end (* No counterpart to second example *) end;; Both of these features have many nice uses. It's almost always possible to work around these omissions, and an SML/NJ extension supports higher-order functors. Object-oriented features Novel object system No special features datatype counter = Counter of { get : unit -> int, set : int -> unit, inc : unit -> counter }; (* Notice that inc's type only reflects that _some_ class type counter = object ('self) * counter is returned, not necessarily "the same method get : int * type of" counter. *) method set : int -> unit method inc : 'self fun myCounter init = end;; let val count = ref init class myCounter init : counter = object in val mutable count = init Counter {get = fn () => !count, method get = count set = fn n => count := n, method set n = count <- n inc = fn () => myCounter (!count + 1) } method inc = {< count = count + 1 >} end; end;; val c = myCounter 23; let c = new myCounter 23;; case c of Counter {set, ...} => set 42; c#set 42;; datatype counter' = Counter' of { class type counter' = object get : unit -> int, inherit counter set : int -> unit, method zero : unit inc : unit -> counter', end;; zero : unit -> unit }; class myCounter' init : counter' = object (self) inherit myCounter init fun myCounter' init = method zero = self#set 0 let end;; val Counter {get, set, inc} = myCounter init in Counter' {get = get, set = set, inc = fn () => myCounter' (get () + 1), zero = fn () => set 0 } end; Most of the individual features that go into a typical concept of "object orientation" are available separately in core ML. The main omissions are succinct mechanisms to implement inheritance and For some situations, objects are the clear right implementation self types. As far as education and training go, lack of "OO" technique, and OCaml makes them convenient to use. features in SML can be a blessing, since new OCaml programmers often latch onto the object system as the default means of abstraction, missing oftentimes more appropriate features in the module system and elsewhere. open in signatures open is allowed in signatures. open is not allowed in signatures. module type S = sig signature S = sig open M val x : M.t val x : t end; end;; [hardha] This can save a lot of time in defining signatures that use [scienti] Just another feature to avoid having to include in the many types defined elsewhere. formal language definition! Separate compilation conventions Filenames imply module names No standard separate compilation scheme When using OCaml as a compiler, every .ml file is treated as a separate module, with its interface optionally given by a corresponding .mli file. This usually works well, but there are some problems. First, the "signatures" defined by .mli files aren't Candidate tools related to SML separate compilation system and first-class module system signatures, so they can't be referenced project management include the SML/NJ Compilation Manager and the anywhere else. This means that multiple source-file-level modules MLton ML Basis system. Both of these are essentially file agnostic can't share a signature, and that signatures must always live inside after all of the appropriate files have been found (and possibly of modules. Of course, one could always put all of his modules in a assigned some compilation flags), effectively concatenating the single file and avoid this problem, but splitting into files is a files together and imposing visibility restrictions at the module standard technique for facilitating good interaction with editors, level. source control, and so on. There is an analogous problem for functors, leading to examples like Map.Make in the standard library, as opposed to the work-alike BinaryMapFn in the SML/NJ library. Tools Build system Command-line tools with help generating dependency information SML/NJ's Compilation Manager; whole-program compilation with MLton SML/NJ's Compilation Manager makes it extremely easy and OCaml integrates well into traditional UNIX build systems. The convenient to compile projects that don't use much non-SML code, ocamldep program builds dependency information for use by Makefiles, including integrated support for ml-lex/yacc. It also has some and the popular (but separately-distributed) OCamlMakefile ties it namespace management features for building and packaging all together. libraries. Build management issues aren't that big a deal for MLton, which must compile whole programs at once, anyway. Bytecode compiler Included with the main distribution Present in some systems, including Moscow ML and the ML Kit Compilation to .NET F# SML.NET F# starts with OCaml, drops the object system and some other features SML.NET matches most of the main advantages of F# but doesn't seem (like parts of the module system), and adds .NET-style OO that to end up with as many neat experimental features. On the other interoperates seamlessly with other .NET languages. There is Visual hand, unlike F# for OCaml, it implements all of Standard ML. It Studio support as well. Lately, there's been a lot of hype also somehow can't match F#'s hype level. surrounding F#. Compilation to Java bytecode OCaml-Java MLj .NET seems to be the managed platform of choice these days for functional programmers, and interest has shifted from MLj to SML.NET. Debugger Backtracking debugger Only Poly/ML includes a mature debugger. [hardha] OCaml features a debugger in the tradition of GDB, plus some [scienti] What, you're still using a debugger instead of unit novel features like backtracking. tests? ;-) Extensible parsing camlp4 extensible grammar tool integrates with OCaml compilation No equivalent camlp4 allows dynamic extension of grammars by adding new non-terminals, in contrast to well-known "macro systems" General objections about macros apply: we would like to based around tokens or s-expressions. Not only does camlp4 keep it as simple as possible for both humans and [hardha] integrate with the OCaml compiler, allowing language [scienti] programs to parse arbitrary SML programs, making it extension along the lines of traditional macro uses, but it undesirable to allow customized grammar extensions. Most can also be used separately. For instance, Coq uses campl4 common uses of macros in C and Lisp are better handled to let users add new commands and tactics implemented in with other SML features. OCaml. Emacs modes caml-mode and tuareg-mode sml-mode Foreign function interfaces Lightweight FFI supported by added language constructs Semi-standardized No Longer Foreign Function Interface, plus MLton's lower-level FFI for interfacing with C code directly The NLFFI embeds most of the C type system in SML, letting the SML The FFI is relatively simple to use once you figure out linking, but compiler type-check appropriate usage and catch many nasty classes the C-level view is one of specialized OCaml types instead of "native of bugs. The ml-nlffigen program builds SML wrappers automatically C" types. This can make it cumbersome to interface with existing C from C header files. The NLFFI tools are available for both SML/NJ code. Also, the programmer needs to write the correct types for and MLton, making it largely seamless to build an FFI-using external functions manually. project with both. The tools take care of adapting to the compilers' different "under the hood" conventions. See also MLton's lower-level FFI. HTML documentation generation from source code ocamldoc Some tool is available and used to generate the Standard Basis documentation, but where is it? Evaluation pending more information on SML tools Optimizing native-code compiler Native code compiler does very little program analysis Whole-program optimizing compiler (MLton) MLton is one of the best open-source optimizing compilers available. The Computer Language Shootout has it in 7th place currently for execution speed, behind D, C, C++, OCaml has gotten by quite well by choosing an efficient Eiffel, Pascal, and Ada compilers and just ahead of [scienti] base compilation strategy. Development focus seems to be on OCaml. The Shootout only measures microbenchmarks, and adding new language features instead of improving [hardha] MLton's whole-program optimization can be expected to compilation. produce a marked efficiency advantage over native OCaml programs for large projects that make good use of abstraction and modularity. [Note in 2020: the Shootout was replaced with The Computer Language Benchmarks Game, which doesn't list SML anymore.] Parser generators ocamllex and ocamlyacc ml-lex and ml-yacc Performance profiling Direct profiling of execution counts for bytecode programs and In MLton, direct profiling of execution counts, time, and indirect gprof-based profiling of time for native code programs allocation for native code programs [scienti] See ocamlprof documentation. [hardha] See MLton profiling documentation. Source/interface browser ocamlbrowser No equivalent ocamlbrowser provides a specialized GUI for navigating through OCaml It's unclear whether many people find ocamlbrowser significantly code. more helpful than Emacs plus highly-hyperlinked HTML documentation. Standard library Standard library plus several other libraries packaged with the main The Standard Basis plus the SML/NJ library distribution Taking the SML/NJ library into account since it is now distributed with MLton as well as SML/NJ, there is no clear winner in standard library coverage between OCaml and SML. Each has all the basics, as well as some gems that the other lacks. Toplevel interactive system Present Present in all SML compilers but MLton Social factors Community contributions to implementations Contributions to the OCaml implementation are tightly regulated, and Generally open community approach. MLton's Subversion repository patches are often rejected. allows commits by any well-established community member who asks for permission. This aspect of OCaml philosophy seems oriented towards ML programmers on average are quite knowledgeable and [scienti] research projects and makes it hard to take advantage of [hardha] skilled at development, so it is advantageous to tap the contributions from well-meaning hackers outside the whole community in developing implementations and project. standard distributions. Cute logos Caml has the camel: [caml] Nothing worth mentioning! Historical roots Historical association with theorem proving tools based on type Historical association with theorem proving tools in the LCF theory and proof terms tradition Caml was developed to use in implementing the Coq theorem prover, as The ML family in general owes its origins to the LCF system. related in this account. Today, SML is associated with successors like Isabelle. Implementation diversity Many Definition-compliant implementations, including HaMLet, MLton A single implementation of the full language , ML Kit, Moscow ML, Poly/ML, SML.NET, Standard ML of New Jersey, and TILT Language design Ad-hoc process similar to most "open source programming languages" Language definition with formal semantics The existence of a language definition helps language OCaml picks up new features agilely, without any heavyweight implementers keep in sync and discourages feature bloat. standardization or formalization process needed for the The formal semantics provides a concrete starting point [hardha] entirety of the revised language before a release is made. [scienti] for formal methods. On the other hand, these aspects The language is in effect defined by some combination of the discourage the adoption of new language features that manual and the implementation. the community might agree on as worthwhile. The new Successor ML project aims to overcome this stagnation, hopefully using more agile processes in the long term. Learning materials Commercial books include Developing Applications with Objective Caml, Commercial books include Introduction to Programming using SML, Practical OCaml, and OCaml for Scientists. Part I of the OCaml manual Elements of ML Programming, and ML for the Working Programmer. contains a tutorial. Online introductions include Programming in Standard ML and Programming in Standard ML '97: An On-Line Tutorial. Library availability Relatively many libraries available Relatively few libraries available OCaml has had the fundamental tools in place to be regarded SML has historically been more the domain of scientists as a "serious programming language" for longer than SML has, with narrow interests in programming languages and [hardha] and its significantly greater number of freely available formal methods, and so fewer libraries are available in libraries today reflects that. The Caml Hump collects links [scienti] general. However, particularly in the MLton community, to these libraries in one place. [Note in 2020: that site is this deficiency is recognized, and directed efforts are gone now, but OPAM got popular.] underway both to draft an enriched "standard library" and to create useful special-purpose libraries. Packaging Pre-built packages available for Debian Linux and many other MLton and SML/NJ packages available for Debian. UNIX-like systems. Research projects extending the language (not just implemented with it) Caml, Flow Caml, F#, G'Caml, JoCaml, MetaOCaml, OCamlDuce Alice, Concurrent ML, Dependent ML, MetaML, MLj, SML.NET Maintained by Adam Chlipala. Last modified November 2020. Created December 2006.