2
votes

In OCaml, it seems that "fun" is the binding operator to me. Does OCaml have built-in substitution? If does, how it is implemented? is it implemented using de Bruijn index?

Just want to know how the untyped lambda-calculus can be implemented in OCaml but did not find such implementation.

3

3 Answers

3
votes

As Bromind, I also don't exactly understand what you mean by saying "Does OCaml have built-in substitution?"

About lambda-calculus once again I'm not really understand but, if you talking about writing some sort of lambda-calculus interpreter then you need first define your "syntax":

(* Bruijn index *)
type index = int

type term =
  | Var of index
  | Lam of term
  | App of term * term

So (λx.x) y will be (λ 0) 1 and in our syntax App(Lam (Var 0), Var 1).

And now you need to implement your reduction, substitution and so on. For example you may have something like this:

(* identity substitution: 0 1 2 3 ... *)
let id i = Var i

(* particular case of lift substitution: 1 2 3 4 ... *)
let lift_one i = Var (i + 1)

(* cons substitution: t σ(0) σ(1) σ(2) ... *)
let cons (sigma: index -> term) t = function
  | 0 -> t
  | x -> sigma (x - 1)

(* by definition of substitution:
       1)  x[σ] = σ(x)
       2)  (λ t)[σ] = λ(t[cons(0, (σ; lift_one))])
               where (σ1; σ2)(x) = (σ1(x))[σ2]
       3)  (t1 t2)[σ] = t1[σ] t2[σ]
*)
let rec apply_subs (sigma: index -> term) = function
  | Var i -> sigma i
  | Lam t -> Lam (apply_subs (function
                              | 0 -> Var 0
                              | i -> apply_subs lift_one (sigma (i - 1))
                             ) t)
  | App (t1, t2) -> App (apply_subs sigma t1, apply_subs sigma t2)

As you can see OCaml code is just direct rewriting of definition.

And now small-step reduction:

let is_value = function
  | Lam _ | Var _ -> true
  | _ -> false

let rec small_step = function
  | App (Lam t, v) when is_value v ->
     apply_subs (cons id v) t
  | App (t, u) when is_value t ->
     App (t, small_step u)
  | App (t, u) ->
     App (small_step t, u)
  | t when is_value t ->
     t
  | _ -> failwith "You will never see me"

let rec eval = function
  | t when is_value t -> t
  | t -> let t' = small_step t in
         if t' = t then t
         else eval t'

For example you can evaluate (λx.x) y:

eval (App(Lam (Var 0), Var 1))
- : term = Var 1
1
votes

OCaml does not perform normal-order reduction and uses call-by-value semantics. Some terms of lambda calculus have a normal form than cannot be reached with this evaluation strategy.

See The Substitution Model of Evaluation, as well as How would you implement a beta-reduction function in F#?.

1
votes

I don't exactly understand what you mean by saying "Does OCaml have built-in substitution? ...", but concerning how the lambda-calculus can be implemented in OCaml, you can indeed use fun : just replace all the lambdas by fun, e.g.: for the church numerals: you know that zero = \f -> (\x -> x), one = \f -> (\x -> f x), so in Ocaml, you'd have

let zero = fun f -> (fun x -> x)
let succ = fun n -> (fun f -> (fun x -> f (n f x)))

and succ zero gives you one as you expect it, i.e. fun f -> (fun x -> f x) (to highlight it, you can for instance try (succ zero) (fun s -> "s" ^ s) ("0") or (succ zero) (fun s -> s + 1) (0)).

As far as I remember, you can play with let and fun to change the evaluation strategy, but to be confirmed...


N.B.: I put all parenthesis just to make it clear, maybe some can be removed.