HL Legal Specifications: Mathematics
Classically, computers have been used for simple arithmetical
computations of number. For this reason, the
machine must also specify some operations of mathematics, as
well as built-in numeric types.
hl specifications define two numeric types - limited-range
integers, and limited-range, limited-precision, exact floating
point numbers. We also define a set of basic operations for
these types, including conversions between these types. Both
numeric types are immutable.
<hl>int built-in type is a signed limited-range integral
type. Implementations are required to provide an integer type
of at least 24 bits including sign; this means that the
implementation should provide a maximum of at least
8,388,607, and a minimum of at least -8,308,608. An
implementation may provide an integer type which supports a
Integers are introduced directly into the emitted bytecode by
the compiler; integers are introduced by the
whose argument is an integer.
Integers are immutable types.
Currently, it is intended that future revisions of the specifications will either define a built-in "BigInt" type, or will be complete enough to support an efficient "BigInt" library written in
hl. A "BigInt" type is an integral type whose range is limited only by the available memory of the physical host machine.
The tradeoffs lie in the implementability of the specifications. If "BigInt" is not a built-in in the specifications of the virtual machine, it is one less thing that new implementations (and these specifications, too) have to worry about; on the other hand, if the goal is an efficient implementation, then having "BigInt" implemented in a library may mean that implementors need to implement serious optimization of the library code.
Integer Type Operations
Integer operations are performed at the lowest level by the Axiomatic layer. Integer operations always require integers as input. There are 5 operations, corresponding to addition, subtraction, multiplication, division, and remainder. These axiomatic special forms are compiled down to their equivalent bytecodes; they evaluate all their arguments.
<axiom>i+ adds two integers,
<axiom>i- subtracts two
<axiom>i* multiplies two integers,
divides two integers, and
<axiom>imod takes the remainder of
dividing two integers. If the inputs given to these axiomatic
special forms are of the incorrect type, the result is
undefined, with the preferred behavior being an error of type
Division by zero (of both
in undefined behavior, with the preferred behavior being an
error of type
<hl>float built-in type is a signed, limited-precision,
limited-range floating-point type. Its precision and range is
currently unspecified, but floating point types must at least be
able to express some numbers from
N / (2 ** M), where N and M
are signed integers of unspecified range and
x ** y means "
raised to the
In the future, we may really need to specify just what the precision and range of the floating-point type is. For now, we informally specify a preference for the IEEE 64-bit floating-point type.
Floats are introduced directly into the emitted code by the
compiler; floats are introduced by the
float bytecode, whose
argument is a floating-point type.
Floats are immutable types.
Floating-point Type Operations
Like the other mathematical operations, floating-point operations are performed at the Axiomatic layer. There are four floating-point operations, corresponding to addition, subtraction, multiplication and division. These special forms are compiled down to their equivalent bytecodes; they evaluate all their arguments.
<axiom>f+ adds two floating-point numbers,
subtracts two floating-point numbers,
<axiom>f* multiples two
floating-point numbers, and
<axiom>f/ divides two
Division by zero from
<axiom>f/ results in undefined behavior,
with the preferred behavior being an error of type
The numeric operations defined in Axiomatic do not allow mixing of numbers of different types. However, there are some axiomatic special forms which perform conversion between integer and floating-point types.
<axiom>i-to-f converts an integer to a floating point type,
<axiom>f-to-i converts a floating point type to an
integer. Conversion from floating point type to integer type
performs truncation (roundoff towards zero). Both special
forms accept a single object of the specified type.
Standard Layer Mathematics
At the Standard layer, the strict typing of mathematical operations is relaxed. Functions defined in the Standard layer use polymorphism in order to perform mathematical operations on numbers of various types.
For addition, subtraction, multiplication, and division, integers are automatically promoted to floats if one of the arguments is a float. Taking the remainder works only for integers.
Further, the Standard layer defines operations to negate and take the reciprocal of a single number.
The Standard layer also defines a set of "hooks" which another
library can further polymorph (for example, by using the
<hl>defm). These "base" functions
may be used directly, but there is generally no need to do so.
Standard Layer Mathematics: Reductors and Dispatchers
The Standard layer divides each operation into two parts: a reductor and a dispatcher. The dispatcher (or "base") function dispatches based on the type of its inputs and accepts exactly two arguments; the reductor accepts at least one argument and performs left-to-right reduction on its argument list. Reductors are named directly by their operation, while dispatchers are prepended with "base".
operation | reductor | dispatcher ----------------+-------------+----------- addition | + | base+ subtraction | - | base- multiplication | * | base* division | / | base/ modulo | mod | base-mod
For example, the
+ or addition operation is handled by the
<hl>+, which then calls the dispatcher
<hl>+ is invoked with exactly two arguments, it is
equivalent to a call to
<hl>+ is called
with more than two arguments, it performs reduction using
(using <hl>v1) (+ a b) := (base+ a b) (+ a b c) := (base+ (base+ a b) c) (+ a b c d) := (base+ (base+ (base+ a b) c) d)
The reductors are not intended to be overloadable by
polymorphing them. Dispatchers are intended to be
overloadable by polymorphism, preferably by
Dispatchers are the functions responsible for correctly
promoting integers to floats; for example, if
called with the first argument of type
<hl>int and the second
argument of type
<hl>float, it is equivalent to:
(using <hl>v1) (base 1 1.0) := (<axiom>+ (<axiom>i-to-f 1) 1.0)
As long as polymorphism only supports monomethods (i.e. functions that dispatch only on their first parameters), user-side polymorphing the dispatchers will not probably be very useful. For implementors: the trick is to use double-dispatch. Unfortunately, the specifications do not mention names for double-dispatching, so any attempt to overload mathematical operations at this time will either be implementation-specific or will have to wait until multimethods are implemented.
The plan is that multimethods (i.e. functions that dispatch on the types of any arguments) will be eventually defined in the future.
When a reductor is invoked with a single argument, the behavior
of the operation depends on the operation. For addition,
multiplication, and modulo, the result is the single argument.
For subtraction, the argument is passed to the dispatcher
<hl>base-neg, which negates the argument. For
division, the argument is passed to the dispatcher function
<hl>base-reciprocal, which produces the reciprocal of the
argument (for an integer argument, this will always result in
(using <hl>v1) (+ a) := a (- a) := (<hl>base-neg a) (* a) := a (/ a) := (<hl>base-reciprocal a) (mod a) := a
<hl>base-reciprocal are intended to be
The default behavior for
(using <hl>v1) (base-neg a) := (base- 0 a) (base-reciprocal a) := (base/ 1 a)
In Standard, division by zero (for division and modulo) results
in an error of type
<hl>value. The default behavior for all
dispatchers, when given objects of types it cannot handle, is to
throw an error of type