SML Syntax Cheatsheet
By David Sun, February 2021
Builtin Types
Six of the builtin types commonly encountered:
Expression  Type 

0  int 
"foo bar"  string 
#" "  char 
true  bool 
1.0  real 
()  unit 
Structured Types
Make tuples and lists using builtin types and themselves.
Expression  Type 

(15,150)  int * int 
[1,2,3,4]  int list 
[["foo","bar"],["baz"]]  string list list 
((true,1),(false,0,()))  (bool * int) * (bool * int * unit) 
[(0,1),(1,0)]  (int * int) list 
([#"a",#"b"],[3.14])  char list * real list 
Note: 1tuples don't exist in Standard ML.
Operators
Operators have different priority levels. Higher priority operations are performed before lower priority operations. The operators *
, +
, and 
work on both int
and real
types.
Operator  Meaning  Priority  Example Expression  Evaluates To  Notes 

*  numeric multiplication  7  8 * 3  24  
/  real division  7  3.0 / 2.0  1.5  operands must be real 
div  integer divison  7  3 div 2  1  operands must be int 
mod  "modulo"  7  8 mod 3  2  operands must be int 
+  numeric addition  6  3 + 4  7  
  numeric subtraction  6  3  ~2  5  ~ denotes negative numbers, e.g. ~5 
^  string combination  6  "foo" ^ "bar"  "foobar"  
::  list construction ("cons")  5  1 :: [2,3,4]  [1,2,3,4]  rightassociative 
@  list combination ("append")  5  [1,2] @ [3,4]  [1,2,3,4]  rightassociative 
Except for ::
and @
, the remaining builtin operators above are leftassociative. Leftassociative operations of equal priority implicitly evaluate from left to right. Rightassociative operations of equal priority implicitly evaluate from right to left.
Expression  Implicit Interpretation  Evaluates To 

3  ~2 + ~5  ((3  ~2) + ~5)  0 
1 :: 2 :: 3 :: []  1 :: (2 :: (3 :: []))  [1,2,3] 
1 :: [] @ 2 :: [] @ 3 :: []  1 :: ([] @ (2 :: ([] @ (3 :: []))))  [1,2,3] 
Boolean Operation
There are three main ones: andalso
, orelse
and not
.
Expression  Evaluates To  Notes 

false andalso true  false  andalso shortcircuits if left operand evaluates to false 
true orelse false  true  orelse shortcircuits if left operand evaluates to true 
not true  false  
not false  true 
Note: See the page about the bool
type here for more information on shortcircuiting behavior.
There are builtin equality operators: =
and <>
.
Operator  Meaning  Priority  Example Expression  Evaluates To 

=  "equal to"  4  1+2 = 41  true 
<>  "not equal to"  4  "a" <> "b"  true 
These two operate on equality types, which include the builtin types mentioned before — and the structured types that can be made from them — excluding real
and function types.
Expression  Evaluates To 

(true,true) = (true,true)  true 
0 = 1 andalso 1 = 1  false 
0 <> 0 orelse 1 <> 1  false 
[1,2,3,4] = [1,2,3,4]  true 
(1,2,"a","b") = (1,2,"a","b")  true 
([1,2,3,4],(["a","b"],[()])) = ([1,2,3,4],(["a","b"],[()]))  true 
0.0 = 0.0  N/A: Not Well Typed 
Note: See the page about the real
type here for more information on why 0.0 = 0.0
is not allowed.
There are builtin comparison operators >
, <
, >=
, and <=
.
Operator  Meaning  Priority  Example Expression  Evaluates To 

>  "greater than"  4  "ba" > "ab"  true 
<  "less than"  4  "ab" < "abc"  true 
>=  "greater than or equal to"  4  #"a" >= #"A"  true 
<=  "less than or equal to"  4  "cab" <= "cba"  true 
These have limited use; they operate on int
, string
, char
, real
.
To build good habits, please practice using the builtin comparison functions Int.compare
, String.compare
, Char.compare
, and Real.compare
to compare their corresponding types instead of exclusively using these equality and comparison operators.
Comparison Functions
There is an order
type with three values: LESS
, EQUAL
, and GREATER
.
Expression  Type 

LESS  order 
EQUAL  order 
GREATER  order 
Int.compare  int * int > order 
String.compare  string * string > order 
Real.compare  real * real > order 
Example use:
Expression  Evaluates To 

Int.compare (~1,0)  LESS 
Int.compare (0,0)  EQUAL 
Int.compare (1,0)  GREATER 
String.compare ("abc","bac")  LESS 
String.compare ("cba","cb")  GREATER 
Real.compare (0.0,0.0)  EQUAL 
Sometimes you want to compare data that is not of basic builtin types, e.g. when sorting lists of tuples. The builtin comparison operators by themselves will not work, but using the order
type allows you to write a comparison function (perhaps using other comparison functions) that defines your own order over that data.
Comments
Comments are denoted using (* *)
.
(* This is a comment. *)
(* This is another comment.
Comments can span multiple lines!
*)
(* This is (* a comment within *) a comment. *)
Value Binding
Use the val
keyword to create variables. It binds values to identifiers (variable names).
val x : int = 5
val (a,b) : int * int = (1,2)
val L : int list = [3,4]
Expression  Evaluates To 

x  5 
a  1 
b  2 
3 * x  15 
2 * x * (5 + 2 * x)  150 
a * x + b  7 
a :: b :: L  [1,2,3,4] 
LetExpressions
Create local bindings (local variables) to compute a let
expression. Place declarations and bindings between the let
in
; the let
expression between the in
end
. Can be nested. The scope of the let
in
declaration is that let
expression's expression.
Expression  Evaluates To 





Lambda Expressions
Write lambda expressions using the fn
keyword — often verbalized as "lambda". A lambda expression is of the form: fn
, pattern, =>
, expression. The lambda expression itself is a value — a value of function type. The =>
in lambda expressions correspond to the >
in their types. The >
arrows are rightassociative infix type constructors denoting function types. Apply lambda expressions via prefix application — before the immediate operand.
Expression  Evaluates To  Type 

(fn (x : int,y : int) => x + y)  (fn (x : int,y : int) => x + y)  int * int > int 
(3,4)  (3,4)  int * int 
(fn (x : int,y : int) => x + y) (3,4)  7  int 
Function Binding
Using a lambda expression more than once requires retyping it. We can give it a name instead. The val
and fun
keywords bind a lambda expression to an identifier, creating a named function. Take note that the =
for binding differs from the =>
reserved word.
(* add : int * int > int *)
val add : int * int > int = fn (x,y) => x + y
(* add : int * int > int *)
fun add (x : int,y : int) : int = x + y
Both function bindings for add
above have the same value: (fn (x,y) => x + y) : int * int > int
.
A named function can be thought of as a lambda expression that has been "identified". The bindings to add
identify (or name) an otherwise anonymous function. Its value is the lambda expression it binds.
Expression  Value  Type 

add  (fn (x,y) => x + y)  int * int > int 
(fn (x,y) => x + y)  (fn (x,y) => x + y)  int * int > int 
add (3,4)  7  int 
(fn (x,y) => x + y) (3,4)  7  int 
Patterns and Case Expressions
Patterns are present in every lambda expression, case
expression, val
, and fun
binding. Every fn
clause, case
clause, and fun
clause contains a pattern with a corresponding expression. A clause is of the form: pattern, =>
, expression. Clauses are delimited by pipes 
.
Expression  Example Clause  Clause Pattern  Clause Expression 













Lambda expressions and case
expressions have the same clause syntax. The clausal patterns must be able to match to the type of the expression being cased on. The clausal expressions must all have the same type (which may be different from that of the expression cased on).
Expression  Example Clause  Clause Pattern  Clause Expression 













The wildcard pattern _
will match to any type, but create no bindings (ignore it).
Candidate  Valid Pattern? 

()  Yes 
0  Yes 
":)"  Yes 
true  Yes 
EQUAL  Yes 
3 + 4  No 
":" ^ ")"  No 
3 < 4  No 
Int.compare (0,0)  No 
Int.compare  No 
0.0  No 
(fn x => x)  No 
x  Yes 
any variable name that is not a reserved word  Yes 
_  Yes 
(0,1)  Yes 
(x,y)  Yes 
(_,_)  Yes 
(x,x)  No 
[]  Yes 
[x]  Yes 
[[[]]]  Yes 
([],[])  Yes 
[] @ []  No 
[x] @ xs  No 
L @ R  No 
x::xs  Yes 
x::y::xs  Yes 
_::_  Yes 
A pattern that accounts for every possible value of the type it matches to is said to perform an exhaustive match. The match is nonexhaustive if and only if a possible value of that pattern's type is missed.
Expression  Pattern Type  Exhaustive Match? 


 Yes 

 No 

 Yes 

 No 

 No 

 Yes 

 No 

 Yes 

 No 

 Yes 

 No 

 Yes 
Using a wildcard for the first clause's entire pattern produces an exhaustive match.
Recursive Function Binding
The rec
reserved word enables a lambda expression to selfreference within its body. The fun
reserved word allows selfreference by default. The clause patterns and expressions in fun
clauses are separated by =
instead of =>
.
val rec length : int list > int = fn [] => 0  _::xs => 1 + length xs
fun length ([] : int list) : int = 0
 length (_::xs : int list) : int = 1 + length xs
As before, both length
bindings have the same value. Don't forget about the lambda!
Expression  Value  Type 










Conditional Expressions
Require a condition that evaluates to true
or false
, after the if
. Two expressions of the same type — one for the then
branch, one for the else
branch. Note that if
then
else
expressions only evaluate one of its two branches — the one it takes.
Expression  Evaluates To 

if 0 <> 1 then "foo" else "bar"  "foo" 
if 0 = 1 then (if true then 1 else 2) else (if false then 3 else 4)  4 
if true then 1 else (1 div 0)  1 
if false then (1 div 0) else 0  0 
op
The op
keyword converts a binary infix operator to binary prefix operation. Priorities are kept the same as before.
Expression  Evaluates To 

(op *) (8,3)  24 
(op +) (3,4)  7 
(op ^) ("foo","bar")  "foobar" 
(op ::) (1,[2,3,4])  [1,2,3,4] 
(op @) ([1,2],[3,4])  [1,2,3,4] 
as
If convenient, we can use the as
keyword between a variable and a structured pattern to reference a structured value both as a whole and by its constituents. The pattern to the left of as
must be a variable. It can be nested. It is always part of a pattern.
val tuple as (a,b) : int * int = (1,2)
Variable Name  Bound To 

a  1 
b  2 
tuple  (1,2) 
val outer as (inner as (a,b),c) : (int * int) * int = ((1,2),3)
Variable Name  Bound To 

outer  ((1,2),3) 
inner  (1,2) 
a  1 
b  2 
c  3 
val L1 as x1::(L2 as x2::(L3 as x3::L4)) : int list = [1,2,3]
Variable Name  Bound To 

L1  [1,2,3] 
x1  1 
L2  [2,3] 
x2  2 
L3  [3] 
x3  3 
L4  [] 