Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OCaml - When To Use Functors

Tags:

ocaml

I'm a little confused about when to use or implement functors in one's code. I included some code below which has two functions display_expr, cal_expr and both of these functions share the same form but differ in implementation. Would this be a place where I would consider creating a single functor which would represent the core functionality of both functions?

type expr =
    | Add of expr * expr
    | Minus of expr * expr
    | Multi of expr * expr
    | Divide of expr * expr
    | Value of int;;

let rec display_expr e =
    match e with
    | Add (a1, a2) -> "(" ^ display_expr a1 ^ " + " ^ display_expr a2 ^ ")"
    | Minus (m1, m2) -> "(" ^ display_expr m1 ^ " - " ^ display_expr m2 ^ ")"
    | Multi (m1, m2) -> "(" ^ display_expr m1 ^ " * " ^ display_expr m2 ^ ")"
    | Divide (d1, d2) -> "(" ^ display_expr d1 ^ " / " ^ display_expr d2 ^ ")"
    | Value v -> string_of_int v;;

let rec cal_expr e =
    match e with
    | Add (a1, a2) -> (cal_expr a1) + (cal_expr a2)
    | Minus (m1, m2) -> (cal_expr m1) - (cal_expr m2)
    | Multi (m1, m2) -> (cal_expr m1) * (cal_expr m2)
    | Divide (d1, d2) -> (cal_expr d1) / (cal_expr d2)
    | Value v -> v;;

let equ =
    Multi(Value 34,
        Add(Value 24,
            Divide(Value 24,
                Minus(Value 10, Value 7)
            )
        )
    );;

Printf.fprintf stdout "%d = %s\n" (cal_expr equ) (display_expr equ);;

Note: I tried writing a functor solution for the above code and I got one working once I figured out that the functor required a common or combined type for the values returned by display_expr and cal_expr.

Also: I'm an extreme OCaml rookie so please consider that in your reply. Thank-you.

like image 716
G4143 Avatar asked Jan 20 '14 00:01

G4143


1 Answers

Functors don't really apply here since there are no modules, but you could attack this quite directly with higher order functions.

The key is to observe that you need one transform, and thus one function argument, for each constructor. This pattern is similar in some ways to to List.fold_right, which can be thought of as an operation that replaces the list constructors.

type expr =
  | Add of expr * expr
  | Sub of expr * expr
  | Mul of expr * expr
  | Div of expr * expr
  | Int of int

let transform ~add ~sub ~mul ~div ~int expr =
  let rec tx = function
    | Add (x, y) -> add (tx x) (tx y)
    | Sub (x, y) -> sub (tx x) (tx y)
    | Mul (x, y) -> mul (tx x) (tx y)
    | Div (x, y) -> div (tx x) (tx y)
    | Int x -> int x in
  tx expr

let binary_op_str sep a b = "(" ^ a ^ sep ^ b ^ ")"

let display_expr = transform
  ~add:(binary_op_str " + ")
  ~sub:(binary_op_str " - ")
  ~mul:(binary_op_str " * ")
  ~div:(binary_op_str " / ")
  ~int:string_of_int

let cal_expr = transform
  ~add:(+)
  ~sub:(-)
  ~mul:( * )
  ~div:(/)
  ~int:(fun x -> x)

Whether this is preferable to your original code is something of a matter of taste.

like image 94
gsg Avatar answered Oct 11 '22 02:10

gsg