A couple of weeks ago I was working on a small program that required generating code in different ways depending on a user option. I was trying to make as few changes as possible. Because of the way the program was created and the language it was written in (not OCaml), it required changing several places in the code.
OCaml functors
I remembered reading a little bit about the concept of a functor in OCaml. Functors are a powerful mechanism that allow you to create modules parameterized by modules. In the case of the task I was working on, I can use it to write the code generation section using module parameterized by a module that provides the final implementation of the code generation.
The example: Code generation via method calls or operators
I’m going to use a simple example of using a functor. Say that we have a representation for a very simple language:
(* ast.ml *)
type expr =
| Plus of expr * expr
| Minus of expr * expr
| Div of expr * expr
| Times of expr * expr
| Call of expr * (expr list)
| Var of string
| Dot of expr * string
I want to have the posibility of generating arithmetic expressions in this language in two ways:
- Generating method calls of arithmetic operations for a Java-like language
- Generate common operators
One example of option #1 is:
var1.multiply(10).plus(var2)
For option #2 is:
var1 * 10 + var2
The code that generates the expressions must not be aware of the strategy that we are using to generate the code. To do this in OCaml we define a signature for the module used to generate the code:
module type GeneratorFuncs = sig
val (+) : Ast.expr -> Ast.expr -> Ast.expr
val (-) : Ast.expr -> Ast.expr -> Ast.expr
val (/) : Ast.expr -> Ast.expr -> Ast.expr
val ( * ) : Ast.expr -> Ast.expr -> Ast.expr
end
We use operators to make it easy to write the code generation. The module that makes the code generator is written as a functor with a parameter that is the module which implements the GeneratorFuncs
signature.
The following code shows the “generator” which generates random arithmetic expressions using the provided module for emitting the code:
module Make_generator(Current_funcs : GeneratorFuncs) = struct
let gen_single() =
if Random.int 10 > 5 then
Ast.Var "x"
else
Ast.Lit (1 + Random.int 5)
let rec generate_sample (depth: int) =
let new_depth = depth - 1 in
Current_funcs.(
match depth, Random.int 10 with
| 0, _ -> gen_single()
| _, r when r >= 0 && r <= 3 ->
((generate_sample new_depth) + (generate_sample new_depth))
| _, r when r >= 4 && r <= 6 ->
((generate_sample new_depth) - (generate_sample new_depth))
| _, r when r >= 7 && r <= 10 ->
((generate_sample new_depth) / (generate_sample new_depth))
| _, _ -> Ast.Var "x")
end
We can write our emitter for generating code using method calls like this:
(* mgenerator.ml *)
module MGenerator = struct
let simple_call (obj: Ast.expr) mmethod arg =
Ast.Call(
Ast.Dot(
obj,
mmethod), [arg])
let (+) (a: Ast.expr) (b: Ast.expr) = simple_call a "plus" b
let ( * ) a b = simple_call a "times" b
let (-) a b = simple_call a "minus" b
let (/) a b = simple_call a "div" b
end
module MGen = Generator.Make_generator(MGenerator)
An alternative module that generates arithmetic expression using operators:
module OGenerator = struct
let (+) (a: Ast.expr) (b: Ast.expr) = Ast.Plus(a, b)
let ( * ) a b = Ast.Times(a, b)
let (-) a b = Ast.Minus(a, b)
let (/) a b = Ast.Div(a, b)
end
module OGen = Generator.Make_generator(OGenerator)
We can use this modules to generate sample code snippets:
let main =
let _ = Random.self_init() in
in let generat = Mgenerator.MGen.generate_sample 3
in let generat2 = Ogenerator.OGen.generate_sample 3
in
print_endline (Ast.pprint_string generat);
print_endline (Ast.pprint_string generat2)
Output:
x.plus(2).plus(5.div(4)).div(x.plus(x).plus(5.div(1)))
x + 4 / x - 2 + 4 + x + 5 + x