Ocaml

Objective Caml is the most popular variant of the Caml language (which is a dialect of the ML language). Its module system is very similar to the SML language. Therefore, we can adopt the same way used in [1].

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 CHECKER =
sig
   module T : TERM
   val check : T.term -> T.term
end

module type OPTIMIZER =
sig
   module T : TERM
   val opt : T.term -> T.term
end

module type EVAL =
sig
   module T : TERM
   val eval : T.term -> int
end

The signature CHECKER specifies a structure that must provide a function check, which has a type of Term.term -> int. However, our product line analysis already identifies that Term itself would be extended to support new terms such as If0, which implies the type of check has to be of ETerm.term -> int where ETerm is a extension of Term. We can postpone writing down any concrete type in a signature definition by using a type specification and referring to it by name.

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

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

module InterpOptFun (C:CHECKER) 
                    (O:OPTIMIZER with type T.term = C.T.term)
                    (E:EVALUATOR with type T.term = O.T.term) =
struct
    let interp = fun e -> E.eval (O.opt (C.check e))
end

For example, 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 BigStep =
struct  
    module T = Term

    let base eval env e = match e with
             T.NUM n -> n
           | T.VAR x -> env x 
           | T.PLUS (e1, e2) -> eval env e1 + eval env e2
           | T.LET (x, e1, e2) -> eval (Env.bind (eval env e1, x, env)) e2

    let eval e = 
	let rec run env e = base run env e
	in run 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 the data type in OCaml is not extensible (NOTE: OCaml supports such extension through objects and polymorphic variants, which will be discussed here), there may appear some code duplication between the structure BigStep and EBigStep as illustrated in the following code:

module EBigStep =
struct  
    module T = ETerm

    let base eval env e = match e with
             T.NUM n -> n
           | T.VAR x -> env x 
           | T.PLUS (e1, e2) -> eval env e1 + eval env e2
           | T.LET (x, e1, e2) -> eval (Env.bind (eval env e1, x, env)) e2
	   | T.IF0 (c, t, f) ->
	     if (eval env c) = 0 then eval env t else eval env f

    let eval e = 
	let rec run env e = base run env e
	in run Env.empty e
end

Then we can instantiate all four interpreters by functor applications. For example, an optimized base interpreter Iopt can be instantiated by applying the functor InterpOptFun to the components Checker, Optimizer and BigStep.

module I = InterpFun (Checker) (BigStep)

module Im = InterpFun (Checker) (Machine)

module Iopt = InterpOptFun (Checker) (Optimizer) (BigStep)

module I’opt = InterpOptFun (EChecker) (EOptimizer) (EBigStep)

Reference

[1] Wonseok Chae and Matthias Blume, “Building a Family of Compilers”, SPLC 2008.


Go to home