Lecture notes 20210519 Lambda Calculus 1

Require Import Coq.ZArith.ZArith.
Require Import Coq.Strings.String.
Require Import PL.RTClosure.
Local Open Scope Z.
Local Open Scope string.

Lambda Expressions

You can play with lambda expressions in programming languages like C++, Ocaml, python and also Coq. In case you do not have their compilers installed (or the installed version is not correct), we provide some web pages that you can use.
  • For C++: https://www.onlinegdb.com/online_c++_compiler .
  • For Ocaml: https://www.jdoodle.com/compile-ocaml-online .
  • Python: https://www.tutorialspoint.com/execute_python_online.php.
Here is some toy programs. You may still remember do_it_three_times.
    // C++
    #include <functional>
    #include <iostream>

    template <typename T>
    std::function<T(T)> do_it_three_times (std::function<T(T)> f) {
        return [f](T x) { return f(f(f(x))); };
    }

    int main() {
        std::function<int(int)> add_one = [](int x) {return x + 1; };
        std::function<int(int)> square = [](int x) {return x * x; };
        std::cout << do_it_three_times (add_one) (1);
        std::cout << std::endl;
        std::cout << do_it_three_times (square) (2);
        std::cout << std::endl;
        std::cout << do_it_three_times (do_it_three_times(add_one)) (0);
        std::cout << std::endl;
        // The following line does not work for C++,
        // because do_it_three_times is a function not an object
        // std::cout << do_it_three_times (do_it_three_times) (add_one) (0);
        std::function<std::function<int(int)>(std::function<int(int)>)>
          do_it_three_times_obj =
          [](std::function<int(int)> f) {return do_it_three_times (f); };
        std::cout << do_it_three_times (do_it_three_times_obj) (add_one) (0);
        std::cout << std::endl;
    }
    (* Ocaml *)
    let do_it_three_times f x = f (f (f x)) in
    let add_one x = x + 1 in
    let square x = x * x in
    Printf.printf "%d\n" (do_it_three_times add_one 1);
    Printf.printf "%d\n" (do_it_three_times square 2);
    Printf.printf "%d\n" (do_it_three_times (do_it_three_times add_one) 0);
    Printf.printf "%d\n" (do_it_three_times do_it_three_times add_one 0);
    # Python
    do_it_three_times = lambda f : lambda x : f (f (f (x)))
    add_one = lambda x : x + 1
    square = lambda x : x * x
    print (do_it_three_times (add_one) (1))
    print "\n"
    print (do_it_three_times (square) (2))
    print "\n"
    print (do_it_three_times (do_it_three_times (add_one)) (0))
    print "\n"
    print (do_it_three_times (do_it_three_times) (add_one) (0))
    print "\n"
Here are syntax trees of lambda expressions with integers and booleans.
Inductive op : Type :=
  | Oplus
  | Ominus
  | Omult
  | Oeq
  | Ole
  | Onot
  | Oand
  | Oifthenelse.

Inductive constant : Type :=
  | int_const (n: Z): constant
  | bool_const (b: bool): constant
  | op_const (o: op): constant.

Inductive tm : Type :=
  | var : string -> tm
  | app : tm -> tm -> tm
  | abs : string -> tm -> tm
  | con : constant -> tm.
Here, abs means function abstraction and app means function application.
Coercion var: string >-> tm.
Coercion op_const: op >-> constant.
Coercion bool_const: bool >-> constant.
Coercion int_const: Z >-> constant.
Coercion con: constant >-> tm.

Example do_it_three_times: tm :=
  abs "f" (abs "x" (app "f" (app "f" (app "f" "x")))).

Example add_one: tm :=
  abs "x" (app (app Oplus "x") 1).

Example square: tm :=
  abs "x" (app (app Omult "x") "x").

Example DITT_ex_1: tm :=
  app (app do_it_three_times add_one) 1.

Example DITT_ex_2: tm :=
  app (app do_it_three_times square) 2.

Example DITT_ex_3: tm :=
  app (app do_it_three_times (app do_it_three_times add_one)) 0.

Example DITT_ex_4: tm :=
  app (app (app do_it_three_times do_it_three_times) add_one) 0.

Operational Semantics

It is critical to see that lambda-calculus does not simply define math functions and values. It also defines how to compute.
  • app (abs x t1) t2 --> t1 [x t2] .
It is called beta reduction.
Now we start to define lambda expressions' small step semantics. In a symtax tree of tm, all internal nodes are abs nodes and app nodes. As we discussed, function abstractions will not be simplified before substitution. Thus the only problem that remains is: how and especially in which order to evaluation function applications.
  • The function part should evaluated first. In other words, if t1 steps to t1', then app t1 t2 steps to app t1' t2.
  • When the function part is already evaluated (e.g. t1 = abs x t1', and t1 = con (op_const Oplus)) and the argument part needs to be evaluated (i.e. tm_pend t1), then the argument part will be evaluated. In other words, if tm_pend t1 and step t2 t2', then step (app t1 t2) (app t1 t2').
  • If both function part t1 and argument part t2 are evaluated, then app t1 t2 will be reduced to the result of function application.
  • If the function part t1 are evaluated and its argument part t2 does not need to be evaluated, then app t1 t2 will be directly reduced to this function application's result.
Inductive tm_base_halt: tm -> Prop :=
  | BH_plus: n: Z, tm_base_halt (app Oplus n)
  | BH_minus: n: Z, tm_base_halt (app Ominus n)
  | BH_mult: n: Z, tm_base_halt (app Omult n)
  | BH_eq: n: Z, tm_base_halt (app Oeq n)
  | BH_le: n: Z, tm_base_halt (app Ole n)
  | BH_and: b: bool, tm_base_halt (app Oand b)
  | BH_if1: b: bool, tm_base_halt (app Oifthenelse b)
  | BH_if2: (b: bool) (t: tm), tm_base_halt (app (app Oifthenelse b) t).

Inductive tm_base_pend: tm -> Prop :=
  | BP_plus: n: Z, tm_base_pend (app Oplus n)
  | BP_minus: n: Z, tm_base_pend (app Ominus n)
  | BP_mult: n: Z, tm_base_pend (app Omult n)
  | BP_eq: n: Z, tm_base_pend (app Oeq n)
  | BP_le: n: Z, tm_base_pend (app Ole n)
  | BP_and_true: tm_base_pend (app Oand true).

Inductive tm_halt: tm -> Prop :=
  | H_abs: x t, tm_halt (abs x t)
  | H_con: c, tm_halt (con c)
  | H_base: t, tm_base_halt t -> tm_halt t.

Inductive tm_pend: tm -> Prop :=
  | P_abs: x t, tm_pend (abs x t)
  | P_con: c, tm_pend (con c)
  | P_base: t, tm_base_pend t -> tm_pend t.

Inductive base_step: tm -> tm -> Prop :=
  | BS_plus: n1 n2: Z, base_step (app (app Oplus n1) n2) (n1 + n2)
  | BS_minus: n1 n2: Z, base_step (app (app Ominus n1) n2) (n1 - n2)
  | BS_mult: n1 n2: Z, base_step (app (app Omult n1) n2) (n1 * n2)
  | BS_eq_true: n1 n2: Z,
                  n1 = n2 -> base_step (app (app Oeq n1) n2) (true)
  | BS_eq_false: n1 n2: Z,
                  n1n2 -> base_step (app (app Oeq n1) n2) (false)
  | BS_le_true: n1 n2: Z,
                  n1n2 -> base_step (app (app Ole n1) n2) (true)
  | BS_le_false: n1 n2: Z,
                  n1 > n2 -> base_step (app (app Ole n1) n2) (false)
  | BS_not: b: bool, base_step (app Onot b) (negb b)
  | BS_and_true: b: bool, base_step (app (app Oand true) b) b
  | BS_and_false: t: tm, base_step (app (app Oand false) t) false
  | BS_if_true: t1 t2: tm,
                  base_step (app (app (app Oifthenelse true) t1) t2) t1
  | BS_if_false: t1 t2: tm,
                  base_step (app (app (app Oifthenelse false) t1) t2) t2
.

Fixpoint subst (x : string) (s : tm) (t : tm) : tm :=
  match t with
  | var x'
      if string_dec x x' then s else t
  | abs x' t1
      abs x' (if string_dec x x' then t1 else subst x s t1)
  | app t1 t2
      app (subst x s t1) (subst x s t2)
  | con c
      con c
  end.

Notation "t [ x ⟼ s ]" := (subst x s t) (at level 10, x at next level).

Inductive step: tm -> tm -> Prop :=
  | S_base: t t',
              base_step t t' -> step t t'
  | S_beta: x t1 t2,
              tm_halt t2 -> step (app (abs x t1) t2) (t1 [ xt2])
  | S_app1: t1 t1' t2,
              step t1 t1' -> step (app t1 t2) (app t1' t2)
  | S_app2: t1 t2 t2',
              tm_pend t1 -> step t2 t2' -> step (app t1 t2) (app t1 t2')
.

Example DITT_result_1:
  clos_refl_trans step
    (app (app do_it_three_times add_one) 1)
    4.
Informally,
  do_it_three_times add_one =
  (fun f x ⇒ f (f (f x))) add_one 1 ⇒
  (fun x ⇒ add_one (add_one (add_one x))) 1 ⇒
  add_one (add_one (add_one 1)) =
  add_one (add_one ((fun x ⇒ x + 1) 1)) ⇒
  add_one (add_one (1 + 1)) ⇒
  add_one (add_one 2) =
  add_one ((fun x ⇒ x + 1) 2) ⇒
  add_one (2 + 1) ⇒
  add_one 3 =
  (fun x ⇒ x + 1) 3 ⇒
  3 + 1 ⇒
  4
Proof.
  unfold do_it_three_times, add_one.
  etransitivity_1n.
  { apply S_app1. apply S_beta. apply H_abs. }
  simpl subst.
  etransitivity_1n.
  { apply S_beta. apply H_con. }
  simpl subst.
  etransitivity_1n.
  {
    apply S_app2; [apply P_abs |].
    apply S_app2; [apply P_abs |].
    apply S_beta.
    apply H_con.
  }
  simpl subst.
  etransitivity_1n.
  {
    apply S_app2; [apply P_abs |].
    apply S_app2; [apply P_abs |].
    apply S_base.
    apply BS_plus.
  }
  simpl Z.add.
  simpl subst.
  etransitivity_1n.
  {
    apply S_app2; [apply P_abs |].
    apply S_beta.
    apply H_con.
  }
  simpl subst.
  etransitivity_1n.
  {
    apply S_app2; [apply P_abs |].
    apply S_base.
    apply BS_plus.
  }
  simpl Z.add.
  etransitivity_1n.
  { apply S_beta. apply H_con. }
  simpl subst.
  etransitivity_1n.
  { apply S_base. apply BS_plus. }
  simpl Z.add.
  reflexivity.
Qed.

(* 2021-05-17 00:32 *)