Primitives

November 30, 2023 · View on GitHub

This is documentation for various SML primitive values and types.

type int

The built-in integer type.

Integers may be positive, negative, or 0. They are usually written with decimal digits.

val pos = 4
val neg = ~8
val zero = 0

They can also be written in hex, with the prefix 0x.

val hex = 0x123beef

The Int structure provides operations on integers.

val three = Int.min (3, 5)

type word

The built-in word type.

Words are unsigned integers. They are written in decimal digits with the prefix 0w.

val dec = 0w123

They can also be written in hex with the prefix 0wx.

val hex = 0wx123beef

The Word structure provides operations on words.

val nine = Word.max (0w4, 0w9)

type real

The built-in real number type.

This type approximates the "mathematical" real numbers by using floating-point arithmetic.

val r = 1.4
val closeToZeroPoint3 = 0.1 + 0.2

Literals can have an exponent with an e or E following the base.

val withExp = 1.4e5

The Real structure provides operations on reals.

val fourPointSeven = Real.min (5.2, 4.7)

type char

The built-in character type.

Literals are written like string literals (delimited with "), except they must:

  • Have length 1.
  • Have a leading #.
val percent = #"%"
val space = #" "

The Char structure provides operations on characters.

val no = Char.isSpace #"g"

The notion of "character" is difficult to define. C programmers may be used to "character" being synonymous with "byte", aka "octet", aka "8 bits", but this is not always the case.

See, for instance, the discussion on the Rust language's primitive character type, or the Unicode standard's definition of "character".

The relevant section of the Definition of Standard ML prescribes that the underlying encoding used in an SML implementation must agree with ASCII:

We assume an underlying alphabet of N characters (N ≥ 256), numbered 0 to N − 1, which agrees with the ASCII character set on the characters numbered 0 to 127.

The common UTF-8 encoding is one such encoding that has this property. Millet, implemented in Rust, requires SML source files be valid UTF-8, because strings in Rust are required to always be valid UTF-8.

For maximum portability, however, an SML program should only use character literals that are ASCII, since the Definition prescribes no further restrictions on the underlying character set other than it must agree with ASCII.

type string

The built-in string type.

Literals are delimited with double quotes, ".

val message = "hello, world!"

There are various string escapes available, all of which start with \.

val () = print "hi\n"

Strings can be concatenated with ^.

val foobar = "foo" ^ "bar"

The String structure provides operations on strings.

val yes = String.isSubstring "erica" "america"

type bool

The built-in boolean type.

There are two boolean values: true and false.

val yes = true
val no = false

Functions that check for equality or perform comparisons, like = and >, often return bool.

val yep = "foo" ^ "bar" = "foobar"
val nope = 3 > 7

if expressions case on the value of a bool.

fun choose (b : bool) = if b then "yea" else "nah"

The Bool structure provides operations on booleans.

val s = Bool.toString false

val true

The bool that represents logical truth. Opposite of false.

val false

The bool that represents logical falsity. Opposite of true.

type 'a list

The built-in list type.

There are two constructors for lists:

  • nil, the empty list.
  • ::, pronounced "cons", a non-empty list.

Cons takes an element x and a list r and returns the list that is that element x followed by r. :: is an right-associative infix operator by default, so you may write x :: r.

val twoFourSix = 2 :: 4 :: 6 :: nil

Lists may be pattern-matched with case and similar constructs.

fun sum (xs : int list) : int =
  case xs of
    nil => 0
  | x :: r => x + sum r

There is syntax sugar for lists, written as:

  • A [ to start the list
  • Comma-separated elements in the list
  • A ] to close the list

For example, all of these things are equivalent:

ThingWhat
[a, b, c]Example list
a :: b :: c :: nilDesugared
a :: (b :: (c :: nil))Explicit right associativity

The List structure provides operations on lists.

val sizes : string list -> int list = List.map String.size
val three = List.length [2, 4, 6]

val nil

The empty list constructor. Identical to [].

val op ::

The non-empty list constructor, pronounced "cons". Defaults to infixr 5.

type 'a ref

The built-in reference type.

This is the core primitive for imperative programming in SML. It is one of the few types in SML whose values are mutable.

  • To construct a ref, use ref.
  • To get the current value in a ref, use !.
  • To set the value in a ref to a new value, use :=.
val r = ref 0
val zero = !r
val () = r := 3
val three = !r

The Ref structure provides operations on references.

val r1 = ref 3
val r2 = ref 8
val () = Ref.swap (r1, r2)
val eight = !r1
val three = !r2

val ref

The sole constructor for the built-in reference type.

type unit

An alias for {}, the empty record/tuple type. It has one value, written () or equivalently {}. Useful for when a function is called only for its side effects and has no useful information to return, like print.

type exn

The type of exceptions, which can be raised or handled.

val op *

Multiplies two numbers.

val op +

Adds two numbers.

val op -

Subtracts the right hand number from the left.

val op /

Divides the left hand real number by the right.

val op <

Returns whether the left hand number, string, or character is less than the right.

val op <=

Returns whether the left hand number, string, or character is less than or equal to the right.

val op >

Returns whether the left hand number, string, or character is greater than the right.

val op >=

Returns whether the left hand number, string, or character is greater than or equal to the right.

val op ~

Negates the number.

val abs

Returns the absolute value of the number.

val div

Divides the left hand number by the right, rounding towards negative infinity.

val mod

Computes the modulus of the left hand number with the right.

val op =

Returns whether the two operands are equal.

val op <>

Returns whether the two operands are unequal.

val use

use is available in some SML implementations, like SML/NJ, but not others, like MLton.

When it is available, usually, the approximate semantics of calling use f2 from a file f1 is to "load" the contents of f2, and bring everything defined by that file f2 into the scope of f1.

A small example:

(* f1.sml *)
val x = 3
(* f2.sml *)
val () = use "f1.sml"
val y = x + 4

use is sometimes used for small-scale multi-file SML projects. For larger projects, consider:

type 'a vector

An immutable sequence with constant-time access.