Polymorphic Variants

Objective Caml provides polymorphic variants which make it possible to write extensible programming. We will explore such a feature to implement a two-way extensible interpreter.

Each component in a reference architecture can be mapped to a OCaml program unit (called module) and interfaces among them can be specified as signatures. Signatures show the types of all components:

module type EVALUATOR =
sig 
    type term
    val eval : term -> int
end

module type CHECKER =
sig 
    type term
    val check : term -> term
end

Then, by using conceptual components (eval and checker), we can specify functors which generate interpreters as in the following code.

module InterpFun (C:CHECKER) 
                 (E:EVALUATOR with type term = C.term) =
struct
    let interp = fun e -> E.eval (C.check e)
end

The functor InterpFun takes two components checker and evaluator with additional constraints defining that the types of term in both components should coincide and then returns their composition.

While there would be only one implementation for each static component, generic components can have multiple implementations. For example, since the component evaluator is generic, it has multiple implementations each of which should match with its signature EVALUATOR. The following code shows an implementation for the Evaluation semantics feature.

module type EVALUATOR0 =
sig 
    type term
    val eval : ((string -> int) * term) -> int
end

module BigStep = struct

    type 'a term0 = [ `Num of int 
                    | `Var of string
                    | `Plus of 'a * 'a
	            | `Let of string * 'a * 'a
	            ]

    module F (X:EVALUATOR0 with type term = private [> 'a term0] as 'a) =
    struct
        type term = X.term term0

        let eval : ((string -> int) * term) -> int = function
             (env, `Num n) -> n
           | (env, `Var x) -> env x
	   | (env, `Plus (e1, e2)) -> X.eval (env, e1) + X.eval (env, e2)
	   | (env, `Let (x, e1, e2)) ->
              X.eval (Env.bind (X.eval (env, e1), x, env), e2)

    end

    module rec L : (EVALUATOR0 with type term = L.term term0) = F (L)
    type term = L.term term0
    let eval e = L.eval (Env.empty, e)
end

As the language grows, so does the evaluation semantics. The structure EBigStep, for example, extends the structure BigStep to support a conditional term. Since OCaml allows polymorphic variants to extend, we can avoid code duplication between the structure BigStep and EBigStep as illustrated in the following code:

module EBigStep = struct

    type 'a term0 = [ 'a BigStep.term0 | `IF0 of 'a * 'a * 'a ]

    module F (X:EVALUATOR0 with type term = private [> 'a term0] as 'a) =
    struct
        type term = X.term term0
        module L = BigStep.F(X)
        let eval : ((string -> int) * term) -> int = function
             (env, (#L.term as e)) -> L.eval (env, e)
	   | (env, `IF0 (c, t, f)) ->
              if (X.eval (env, c) = 0) then X.eval (env, t) 
	      else X.eval (env, f) 
    end

    module rec L : (EVALUATOR0 with type term = L.term term0) = F (L)
    type term = L.term term0
    let eval e = L.eval (Env.empty, e)
end

Then we can instantiate different interpreters by functor applications.

module I = InterpFun (Checker) (BigStep)

module Im = InterpFun (EChecker) (EBigStep)

Reference

[1] Jacques Garrigue, Private rows: abstracting the unnamed. In 4th Asian Symposium on Programming Languages and Systems, Sydney, November 2006. Springer-Verlag LNCS 4279.


Go to home