Delimited overloading

Table of contents
pa_do
Macro
pa_infix
Contributing

Public areas
API (ocamldoc)
Download
Summary
News
Tracker





Delimited overloading is a syntax extension to ease the writing of efficient arithmetic expressions in OCaml. This project was sponsored by Jane Street Capital during their OCaml Summer Project 2008. We are very grateful for their support. See the complete proposal (PDF).

This project is hosted on OCamlForge.

Description

This project provides three syntax extensions provided by several modules. The information below should give you a taste of the possibilities of these syntax extensions but the main reference is the ocamldoc help.

pa_do

This module allows to locally overload operators and functions. The syntax is X.(e) where X is a module name and e is an expression. For example, you can write

Big_int.( 23 ** 567 mod 45 + "123456789123456789123456789")
Int64.(float(q * of_int n - (s * s) lsr 3)) /. float n
Int32.(incr x)
and this will be transformed into the appropriate Big_int, Int64, and Int32 function calls (the string will be checked to represent a valid Big_int at compile time). The standard OCaml numeric modules Int32, Int64, Nativeint, Complex (provided by the Pa_do.Std module), Num, Big_int and Ratio (in a separate extension pa_do_num because they require the nums.cma library (ocamlfind package num) to be loaded in camlp4 for static checking) are overloaded, as well as two "phantom" modules Int and Float. The Complex module use the name I to denote the imaginary unit so one can write
Complex.(log(z + 2I) = I / u)

You can easily define your own overloadings by either using the concrete syntax or the API. As an example, suppose you have a module X that defines the usual arithmetic functions, add, sub, mul, div and the unary negation neg. To be able to write expressions using the usual arithmetic operators, like X.(x * y + z), simply put

OVERLOAD_ARITHMETIC X
at the beginning of your source file. As another example, let us use the Rope library. To make it more like strings, one can declare
OVERLOAD_STRING Rope(of_string)
OVERLOAD_COMPARISON Rope(compare)
OVERLOAD Rope((^) -> concat2)
OVERLOAD_STRING_GET Rope(get)
With this one can just write Rope.(if max s u <= "pre" ^ u then s.[0] else u.[0]). Finally, for a polynomial module Poly on the field of the reals, one probably would like to use (the name of the functions have been chosen to be self-documenting):
OVERLOAD_INT Poly(constant_int_poly)
OVERLOAD_FLOAT Poly(constant_poly)
OVERLOAD_ARITHMETIC Poly
OVERLOAD Poly((=) -> equal; (<>) -> not_equal; ( ** ) -> pow)
OVERLOAD_POLY_VAR Poly(var_of_string)
Then one can use the natural looking let p = Poly.(1 + `x * `x + `x * `y + `x**k) to declare the polynomial q with variables x and y. Note that the library caches the evaluation of constants so there will actually be only a single var_of_string "x" for the entire source file. To support Poly.(`x**2), where 2 has not to be transformed into a polynomial, one has to add a new rule with the constants function from the API.

Of course, to avoid putting the OVERLOAD declarations in every source file, one would usually prefer to write a syntax extension module and perform these tasks with the Delimited overloading API.

Macro

Pa_do.Macro enables macros compatible with delimited overloading—and with other syntax extensions by means of a clear API. They provide a way write ad-hoc polymorphic code. They also correct some design flaws of the original macros (especially the interaction of IFDEF and DEFINE).

As a simple example of what macros enable you to do, here is a generic "incrementor":

DEFINE INCR(M,x) = M.(x := !x + 1)
You can then use it with INCR(Int, x), INCR(Float, x),... As a more interesting example, here is a generic implementation of a Runge-Kutta scheme of order 4 parametrized by a vector space V (assuming that "+" is overloaded as the vector addition and "*" denotes the scalar multiplication with the floats first):
DEFINE RK4(M) =
  let onesixth = 1. /. 6.
  and onethird = 1. /. 3. in
  fun ?(n=200) f t0 t1 x0 ->
    let h = (t1 -. t0) /. float n in
    let xt = Array.make (n + 1) x0 in
    M.(
      for i = 0 to Int.(n-1) do
        let ti = t0 +. float i *. h
        and xi = xt.(i) in
        let ti5 = ti +. 0.5 *. h in
        let k1 = h * f ti xi in
        let k2 = h * f ti5 (xi + 0.5 * k1) in
        let k3 = h * f ti5 (xi + 0.5 * k2) in
        let k4 = h * f (ti +. h) (xi + k3) in
        xt.(Int.(i + 1)) <- xi + onesixth * (k1 + k4) + onethird * (k2 + k3)
      done);
    xt

pa_infix

The purpose of pa_infix is to be able to say, for any OCaml operator symbol, whether one wants it as prefix, postfix or binary infix. It also allows to set the associativity and precedence of operators. The easier way to use it is to use the concrete syntax. For example to turn <+> into an binary infix operator at the same level as +, you just write in your source code:

INFIX ( <+> ) LEVEL ( + )
let ( <+> ) x y = (* definition of the operation *)
To turn ++ into a post operator — thus binding stronger than function application — simply use
POSTFIX ( ++ )
You can then, for example, define it with let ( ++ ) x = x + 1 and use it: let z = 3 ++. This extension also allows to define purely alphabetical operators (no digits, no underscore, no prime):
INFIX subset LEVEL ( && )
Such an operator is used as the standard OCaml alphabetical operators such as lsl: define it with let ( subset ) = ... and use it with a subset x. Beware that these operators become keywords and can therefore not be used as variable names anymore.

If your definitions may be useful in several source files, we recommend you create a syntax extension module instead and use the API.




Getting the development version and contributing

This project uses bzr as its source control management. You can download the source and its history with

bzr branch http://bzr.ocamlcore.org/pa-do/pa-do/trunk pa-do
You can then hack on the code, commiting your modifications, and then submit your changes with
bzr bundle > file.patch
(and post file.patch to the one of the trackers) or
bzr send
(and email the bundle to one of the developers).

For your convenience, a copy of the history is kept in the SVN repository (it may be updated slightly less frequently than the bzr repository). Here is more information on how to use bzr with OCamlForge.