Module 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.

Integer arithmetic

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.

val (+) : int -> int -> int

Integer addition

val (-) : int -> int -> int

Integer subtraction

val (*) : int -> int -> int

Integer multiplication

val (/) : int -> int -> int

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.

val (mod) : int -> int -> int

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.

val abs : int -> int

Integer absolute value

Precedence and Associativity

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.

Floating-point arithmetic

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.

val (+.) : float -> float -> float

Floating-point addition

val (-.) : float -> float -> float

Floating-point subtraction

val (*.) : float -> float -> float

Floating-point multiplication

val (/.) : float -> float -> float

Floating-point division

val (**) : float -> float -> float

Exponentiation

val sqrt : float -> float

Square root

val exp : float -> float

Exponential

val log : float -> float

Natural logarithm

val ceil : float -> float

Round up to the nearest integer (returned as a float)

val floor : float -> float

Round down to the nearest an integer

val abs_float : float -> float

Floating-point absolute value

Boolean operations

val not : bool -> bool

Boolean negation

val (&&) : bool -> bool -> bool

Boolean conjunction (and)

val (||) : bool -> bool -> bool

Boolean disjunction (or)

Comparisons

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.

val (=) : 'a -> 'a -> bool

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.

val (<>) : 'a -> 'a -> bool

Structural inequality, i.e., x <> y is equivalent to not (x = y)

val (<) : 'a -> 'a -> bool

Structural less-than

val (>) : 'a -> 'a -> bool

Structural greater-than

val (<=) : 'a -> 'a -> bool

Structural less-than-or-equal-to, i.e., x <= y is equivalent to not (x > y)

val (>=) : 'a -> 'a -> bool

Structural greater-than-or-equal-to

val min : 'a -> 'a -> 'a

Minimum of its two arguments

val max : 'a -> 'a -> 'a

Maximum of its two arguments

Conversions

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.

val float_of_int : int -> float

Converts an integer into a floating-point number

val int_of_float : float -> int

Converts a floating-point number into an integer by rounding towards 0

val int_of_char : char -> int

Converts a character into its ASCII code

val char_of_int : int -> char

Converts an ASCII code into a character, raising an Invalid_argument exception if its argument is not a valid ASCII code

val string_of_bool : bool -> string

Converts a Boolean value to a string

val bool_of_string : string -> bool

Converts a string to a Boolean value, raising a Failure exception if the argument doesn't evaluate to "true" or "false"

val bool_of_string_opt : string -> bool option

Same as the previous function, but is None in the case of failure

val string_of_int : int -> string

Converts an integer to a string

val int_of_string : string -> int

Converts a string to an integer, raising a Failure exception if the argument doesn't represent a integer

val int_of_string_opt : string -> int option

Same as the previous function, but is None in the case of failure

val string_of_float : float -> string

Converts an floating-pofloat number to a string

val float_of_string : string -> float

Converts a string to an floating-pofloat number, raising a Failure exception if the argument doesn't represent a floating-pofloat number

val float_of_string_opt : string -> float option

Same as the previous function, but is None in the case of failure

Input/Output

Printing

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.

val print_int : int -> unit

Prints an integer to standard output

val print_float : float -> unit

Prints a floating-point number to standard output

val print_string : string -> unit

prints a string to standard output

val print_endline : string -> unit

Prints a string followed by a newline character to standard output

val print : string -> unit

print is an alias for print_endline

Reading

We'll primarily be using reading functions to read in files when we build interpreters.

val read_line : unit -> string

Reads a line (upto a newline character) from standard input, where the output does not include a the newline character

val read : unit -> string

Reads everything from standard input

Basic OCaml Types

Functions

val (|>) : 'a -> ('a -> 'b) -> 'b

x |> f |> g is equivalent to g (f x). This operator is useful for pipelining

Integers

module Int : sig ... end

A module containing printers for integers

Floats

module Float : sig ... end

A module containing printers for floats, and the value pi

Booleans

module Bool : sig ... end

A module containing printers for Boolean values

Characters

module Char : sig ... end

A module containing printers for characters

Strings

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.

val (^) : string -> string -> string

String concatenation

let _ = assert ("hello" ^ "world" ^ "!" = "hello world!")
module String : sig ... end

A module containing basic string operations

Lists

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)
val (@) : 'a list -> 'a list -> 'a list

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

Options

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.

Results

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.

type ('a, 'b) result =
  1. | Ok of 'a
  2. | Error of 'b
module Result : sig ... end

A module containing basic result operations

Nonempty Trees

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.

type 'a ntree =
  1. | Node of 'a * 'a ntree list
module Ntree : sig ... end

A module containing printers for trees

Exception Handling

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.

val raise : exn -> 'a

Raise an exception

val failwith : string -> 'a

Raise a Failure exception with a given message

Extra Utilities

Trigonometric Functions

val cos : float -> float
val sin : float -> float
val tan : float -> float
val acos : float -> float
val asin : float -> float
val atan : float -> float
val atan2 : float -> float -> float
val hypot : float -> float -> float
val cosh : float -> float
val sinh : float -> float
val tanh : float -> float
val acosh : float -> float
val asinh : float -> float
val atanh : float -> float

Randomness

module Random : sig ... end

A module containing basic operations for randomness