Specifications.Stdlib320
The following is the standard library we'll use for the first half of CAS CS 320: Concepts of Programming Languages at Boston University (Spring 2025). It's a very small subset of the OCaml Standard Library with a bit more documentation (it also gives us more control over what functions are "allowed" for assignments). Nearly everything here is also available in the OCaml Standard Library, so check the Stdlib
documentation for more information on what you see below.
Like most programming languages, OCaml has infix arithmetic operators. That said, the following operators can only be applied to integers. Arithmetic operations for floating-point numbers are defined in the next section. There are no arithmetic operators for any other type (strings, lists, etc.)
In OCaml, infix operators can be used as (Curry-ed) prefix operators by surrounding them in parentheses, e.g., (+) 11 13
has the same value as 11 + 13
. Any function below whose name is surrounded in parentheses can be used as an infix binary operator.
Integer division in OCaml is implemented as Euclidean division, which rounds towards 0
(this differs from integer division in Python, which is implemented as floored division).
let _ = assert (5 / 3 = 1)
let _ = assert ((-5) / 3 = -1)
let _ = assert (5 / (-3) = -1)
Raises a Division_by_zero
exception if the second argument evaluates to 0
.
Integer remainder is implemented so as to satisfy the following properties:
x mod y = x mod (abs y)
(- x) mod y = - (x mod y)
For positive integers it's the remainder of x / y
in the grade school sense:
x / y * y + x mod y = x
Raises a Division_by_zero
exception if the second argument evaluates to 0
.
Once we have binary operators, we have to discuss precedence and associativity. This will be a formal topic when we cover parsing. For now, we'll piggy-back on your intuitions about mathematical expressions from grade school.
We can surround an expression with parentheses in order to use it as an operand of an infix binary operator, e.g., the expression (1 + 2) * 3
has the subexpression 1 + 2
as its left operand. If we leave out the parentheses, the expression is ambiguous; it's unclear a priori if 1 + 2 * 3
should have the same value as (1 + 2) * 3
or 1 + (2 * 3)
. We assign operators precedence and associativity in order to resolve ambiguities.
One way to eliminate the ambiguity in the example above it to rank operators by precedence. Operators are evaluated in order of decreasing precedence, i.e., operators with higher precedence are evaluated first. It's also said that operators of higher precedence "bind tighter" than operators of lower precedence. More familiarly, you may have learned the acronym PEMDAS, which defines the order of operations (i.e., relative precedence of operators) for arithemtic expressions. For the above example, the expression 1 + 2 * 3
should have the same value as the expression 1 + (2 * 3)
.
Given precedence, it's still possible for there to be ambiguity with operators of the same precedence, e.g., it's unclear a priori if 1 - 2 - 3
should have the same value as (1 - 2) - 3
or 1 - (2 - 3)
. We eliminate this ambiguity by assigning operators associativity, either left, right, or no associativity. For left associative operators, parentheses group to the left, i.e., operators furthest left are evaluated first. So 1 - 2 - 3
has the same value as (1 - 2) - 3
. The analogous is true for the right associativity operators.
We're eliding some details, but hopefully this should make sufficiently clear how to read expressions with binary operators in OCaml. For full details on precedence and associativity of built-in operators in OCaml, see the OCaml manual page on operators.
From the OCaml Standard Library: "OCaml's floating-point numbers follow the IEEE 754 standard, using double precision (64 bits) numbers. Floating-point operations never raise an exception on overflow, underflow, division by zero, etc. Instead, special IEEE numbers are returned as appropriate..." See this paragraph for more details.
Note that the first four arithmetic operators have a '.
' following them. This distinguishes them from the corresponding arithmetic operators for integers. OCaml does not support operator overloading.
The comparison operators in OCaml are polymorphic. This means any two expressions of the same type may be compared, with the exception of functions; comparison operators raise an Invalid_argument
exception if two functions are compared. Comparison operators behave as expected on Boolean values, strings, lists, options, etc.
Structural equality. This is ==
in most other languages. As an aside, you should generally avoid using this for floating-point numbers, and should instead compare floating point numbers up to a given tolerance.
Structural less-than-or-equal-to, i.e., x <= y
is equivalent to not (x > y)
Unlike many other programming languages, OCaml does not support implicit type conversion; all conversions must be done explicitly. The OCaml standard library includes a small collection of useful conversions.
Converts a floating-point number into an integer by rounding towards 0
Converts an ASCII code into a character, raising an Invalid_argument
exception if its argument is not a valid ASCII code
Converts a string to a Boolean value, raising a Failure
exception if the argument doesn't evaluate to "true"
or "false"
Same as the previous function, but is None
in the case of failure
Converts a string to an integer, raising a Failure
exception if the argument doesn't represent a integer
Same as the previous function, but is None
in the case of failure
Converts a string to an floating-pofloat number, raising a Failure
exception if the argument doesn't represent a floating-pofloat number
Same as the previous function, but is None
in the case of failure
One complaint we frequently receive about OCaml is that it's difficult to do intermediate printing. If you want to print something in the definition of a function, you just have to create a dummy variable and use a printing function.
let add_one (x : int) : int =
let _ = print (string_of_int x) in
x + 1
There are better ways of doing this but I recommend this approach to beginners.
There is no generic printing function (we don't want one). That said, we've included additional printing functions in the individual modules for each types, e.g.,
let add_one (x : int) : int =
let _ = Int.print x in
x + 1
We've included the following printing functions because they're included in the standard library.
Prints a string followed by a newline character to standard output
We'll primarily be using reading functions to read in files when we build interpreters.
Reads a line (upto a newline character) from standard input, where the output does not include a the newline character
x |> f |> g
is equivalent to g (f x)
. This operator is useful for pipelining
module Int : sig ... end
A module containing printers for integers
module Float : sig ... end
A module containing printers for floats, and the value pi
module Bool : sig ... end
A module containing printers for Boolean values
module Char : sig ... end
A module containing printers for characters
Strings in OCaml are sequences of bytes. It's important to note that string are not lists of characters, and it is generally good practice not to think of them as such. We don't do much string manipulation in this course (but we will do some). See the OCaml docs on Strings for more details.
String concatenation
let _ = assert ("hello" ^ "world" ^ "!" = "hello world!")
module String : sig ... end
A module containing basic string operations
Lists are ordered sequences of elements of the same type. They are not the same as arrays. They are defined by the following ADT.
type 'a t = 'a list =
| []
| (::) of 'a * 'a list
let empty_list = []
let example_cons = 1 :: 2 :: 3 :: []
let example_lit = [1;2;3]
let _ = assert (example_cons = example_lit)
List concatenation
let _ = assert ([[1;2;3] @ [4;5;6] @ [7;8;9] = [1;2;3;4;5;6;7;8;9]])
module List : sig ... end
A module containing basic list operations
An option is like a box that might contain a value. It is given by the following ADT:
type 'a option = None | Some of 'a
They are used for defining partial functions, i.e., functions which are not defined on all inputs. For example, we can write the following function which returns the first element of a list, given the list is nonempty.
let first (l : 'a list) : 'a option =
match l with
| [] -> None
| x :: _ -> Some x
If l
is empty, first l
is None
. This is not the same thing as a null pointer. None
is a constructor for a datatype. Same with Some
; in particular, Some 2
is not the same as 2
(they're not even the same type).
module Option : sig ... end
A module containing basic operations for options.
A result is the same as an option except that the "none" case carried data. It's given by the following ADT. As with options, they are used to define partial functions, but particularly in the case that we want more information in the error case.
module Result : sig ... end
A module containing basic result operations
Nonempty trees are not a part of the OCaml standard library, but they're useful to have. In particular, we've included a printer for nonempty trees which will be useful for visualizing heirarchical data. Trees are given by the following ADT.
These are nonempty n
-ary trees. Note that there is no "leaf" case; a leaf is a node with an empty list of children.
module Ntree : sig ... end
A module containing printers for trees
We won't do too much error handling in this course, but we will occasionally throw exceptions in the interpreters we write. See the OCaml docs on error handling and OCP section on Exceptions for more details.
module Random : sig ... end
A module containing basic operations for randomness