ocamlocaml-core

Why does OCaml think that this function takes an int parameter when nothing suggests that it should be the case?


I was working on chapter 1 of Modern Compiler Implementation in ML by Andrew Appel and I decided to implement it in OCaml instead of SML. I'm new to OCaml and I came across a very frustrating problem. OCaml seems to think that the below function has the signature int * (int * 'a) -> 'a option.

let rec lookupTable = function
  | name, (i, v) :: _ when name = i -> Some v
  | name, (_, _) :: rest -> lookupTable (name, rest)
  | _, [] -> None

But as far as I can tell, there should be nothing that suggests that the first element in the tuple is an int. This is a problem because when the lookupTable function down the line, the compiler complains that I am not passing it an integer. Perhaps I am missing something incredibly obvious, but it has been pretty mind-boggling. Here is the rest of the program

open Base

type id = string
type binop = Plus | Minus | Times | Div

type stm =
  | CompoundStm of stm * stm
  | AssignStm of id * exp
  | PrintStm of exp list

and exp =
  | IdExp of id
  | NumExp of int
  | OpExp of exp * binop * exp
  | EseqExp of stm * exp

(* Returns the maximum number of arguments of any print
   statement within any subexpression of a given statement *)
let rec maxargs s =
  match s with
  | CompoundStm (stm1, stm2) -> Int.max (maxargs stm1) (maxargs stm2)
  | AssignStm (_, exp) -> maxargs_exp exp
  (* Might be more nested expressions *)
  | PrintStm exps -> Int.max (List.length exps) (maxargs_explist exps)

and maxargs_exp e = match e with EseqExp (stm, _) -> maxargs stm | _ -> 0

and maxargs_explist exps =
  match exps with
  | exp :: rest -> Int.max (maxargs_exp exp) (maxargs_explist rest)
  | [] -> 0

type table = (id * int) list

let updateTable name value t : table = (name, value) :: t

let rec lookupTable = function
  | name, (i, v) :: _ when name = i -> Some v
  | name, (_, _) :: rest -> lookupTable (name, rest)
  | _, [] -> None

exception UndefinedVariable of string

let rec interp s =
  let t = [] in
  interpStm s t

and interpStm s t =
  match s with
  | CompoundStm (stm1, stm2) -> interpStm stm2 (interpStm stm1 t)
  | AssignStm (id, exp) ->
      let v, t' = interpExp exp t in
      updateTable id v t'
  (* Might be more nested expressions *)
  | PrintStm exps ->
      let interpretAndPrint t e =
        let v, t' = interpExp e t in
        Stdio.print_endline (Int.to_string v);
        t'
      in
      List.fold_left exps ~init:t ~f:interpretAndPrint

and interpExp e t =
  match e with
  | IdExp i -> (
      match lookupTable (i, t) with
      | Some v -> (v, t)
      | None -> raise (UndefinedVariable i))
  | NumExp i -> (i, t)
  | OpExp (exp1, binop, exp2) ->
      let exp1_val, t' = interpExp exp1 t in
      let exp2_val, _ = interpExp exp2 t' in
      let res =
        match binop with
        | Plus -> exp1_val + exp2_val
        | Minus -> exp1_val - exp2_val
        | Times -> exp1_val * exp2_val
        | Div -> exp1_val / exp2_val
      in
      (res, t')
  | EseqExp (s, e) -> interpExp e (interpStm s t)


Solution

  • Base defines = as int -> int -> bool, so when you have the expression name = i the compiler will infer them as ints.

    You can access the polymorphic functions and operators through the Poly module, or use a type-specific operator by locally opening the relevant module, e.g. String.(name = i).

    The reason Base does not expose polymorphic operators by default is briefly explained in the documentation's introduction:

    The comparison operators exposed by the OCaml standard library are polymorphic:

    What they implement is structural comparison of the runtime representation of values. Since these are often error-prone, i.e., they don't correspond to what the user expects, they are not exposed directly by Base.

    There's also a performance-argument to be made, because the polymorphic/structural operators need to also inspect what kind of value it is at runtime in order to compare them correctly.