| ## Function Abstraction and Evaluation | |
| We have seen that if we have ``m n : Nat``, then we have ``(m, n) : Nat × Nat``. | |
| This gives us a way of creating pairs of natural numbers. | |
| Conversely, if we have ``p : Nat × Nat``, then | |
| we have ``p.1 : Nat`` and ``p.2 : Nat``. | |
| This gives us a way of "using" a pair, by extracting its two components. | |
| We already know how to "use" a function ``f : α → β``, namely, | |
| we can apply it to an element ``a : α`` to obtain ``f a : β``. | |
| But how do we create a function from another expression? | |
| The companion to application is a process known as "lambda abstraction." | |
| Suppose that giving a variable ``x : α`` we can construct an expression ``t : β``. | |
| Then the expression ``fun (x : α) => t``, or, equivalently, ``λ (x : α) => t``, is an object of type ``α → β``. | |
| Think of this as the function from ``α`` to ``β`` which maps any value ``x`` to the value ``t``, | |
| which may depend on ``x``. | |
| ```lean | |
| #check fun (x : Nat) => x + 5 | |
| #check λ (x : Nat) => x + 5 | |
| #check fun x : Nat => x + 5 | |
| #check λ x : Nat => x + 5 | |
| ``` | |
| Here are some more examples: | |
| ```lean | |
| constant f : Nat → Nat | |
| constant h : Nat → Bool → Nat | |
| #check fun x : Nat => fun y : Bool => h (f x) y -- Nat → Bool → Nat | |
| #check fun (x : Nat) (y : Bool) => h (f x) y -- Nat → Bool → Nat | |
| #check fun x y => h (f x) y -- Nat → Bool → Nat | |
| ``` | |
| Lean interprets the final three examples as the same expression; in the last expression, | |
| Lean infers the type of ``x`` and ``y`` from the types of ``f`` and ``h``. | |
| Some mathematically common examples of operations of functions can be described in terms of lambda abstraction: | |
| ```lean | |
| constant f : Nat → String | |
| constant g : String → Bool | |
| constant b : Bool | |
| #check fun x : Nat => x -- Nat → Nat | |
| #check fun x : Nat => b -- Nat → Bool | |
| #check fun x : Nat => g (f x) -- Nat → Bool | |
| #check fun x => g (f x) -- Nat → Bool | |
| ``` | |
| Think about what these expressions mean. The expression ``fun x : Nat => x`` denotes the identity function on ``Nat``, | |
| the expression ``fun x : α => b`` denotes the constant function that always returns ``b``, | |
| and ``fun x : Nat => g (f x)``, denotes the composition of ``f`` and ``g``. | |
| We can, in general, leave off the type annotation on a variable and let Lean infer it for us. | |
| So, for example, we can write ``fun x => g (f x)`` instead of ``fun x : Nat => g (f x)``. | |
| We can abstract over the constants `f` and `g` in the previous definitions: | |
| ```lean | |
| #check fun (g : String → Bool) (f : Nat → String) (x : Nat) => g (f x) | |
| -- (String → Bool) → (Nat → String) → Nat → Bool | |
| ``` | |
| We can also abstract over types: | |
| ```lean | |
| #check fun (α β γ : Type) (g : β → γ) (f : α → β) (x : α) => g (f x) | |
| ``` | |
| The last expression, for example, denotes the function that takes three types, ``α``, ``β``, and ``γ``, and two functions, ``g : β → γ`` and ``f : α → β``, and returns the composition of ``g`` and ``f``. (Making sense of the type of this function requires an understanding of dependent products, which we will explain below.) Within a lambda expression ``fun x : α => t``, the variable ``x`` is a "bound variable": it is really a placeholder, whose "scope" does not extend beyond ``t``. | |
| For example, the variable ``b`` in the expression ``fun (b : β) (x : α) => b`` has nothing to do with the constant ``b`` declared earlier. | |
| In fact, the expression denotes the same function as ``fun (u : β) (z : α), u``. Formally, the expressions that are the same up to a renaming of bound variables are called *alpha equivalent*, and are considered "the same." Lean recognizes this equivalence. | |
| Notice that applying a term ``t : α → β`` to a term ``s : α`` yields an expression ``t s : β``. | |
| Returning to the previous example and renaming bound variables for clarity, notice the types of the following expressions: | |
| ```lean | |
| #check (fun x : Nat => x) 1 -- Nat | |
| #check (fun x : Nat => true) 1 -- Bool | |
| constant f : Nat → String | |
| constant g : String → Bool | |
| #check | |
| (fun (α β γ : Type) (g : β → γ) (f : α → β) (x : α) => g (f x)) Nat String Bool g f 0 | |
| -- Bool | |
| ``` | |
| As expected, the expression ``(fun x : Nat => x) 1`` has type ``Nat``. | |
| In fact, more should be true: applying the expression ``(fun x : Nat => x)`` to ``1`` should "return" the value ``1``. And, indeed, it does: | |
| ```lean | |
| #reduce (fun x : Nat => x) 1 -- 1 | |
| #reduce (fun x : Nat => true) 1 -- true | |
| constant f : Nat → String | |
| constant g : String → Bool | |
| #reduce | |
| (fun (α β γ : Type) (g : β → γ) (f : α → β) (x : α) => g (f x)) Nat String Bool g f 0 | |
| -- g (f 0) | |
| ``` | |
| The command ``#reduce`` tells Lean to evaluate an expression by *reducing* it to its normal form, | |
| which is to say, carrying out all the computational reductions that are sanctioned by its kernel. | |
| The process of simplifying an expression ``(fun x => t) s`` to ``t[s/x]`` -- that is, ``t`` with ``s`` substituted for the variable ``x`` -- | |
| is known as *beta reduction*, and two terms that beta reduce to a common term are called *beta equivalent*. | |
| But the ``#reduce`` command carries out other forms of reduction as well: | |
| ```lean | |
| constant m : Nat | |
| constant n : Nat | |
| constant b : Bool | |
| #reduce (m, n).1 -- m | |
| #reduce (m, n).2 -- n | |
| #reduce true && false -- false | |
| #reduce false && b -- false | |
| #reduce b && false -- Bool.rec false false b | |
| #reduce n + 0 -- n | |
| #reduce n + 2 -- Nat.succ (Nat.succ n) | |
| #reduce 2 + 3 -- 5 | |
| ``` | |
| We explain later how these terms are evaluated. | |
| For now, we only wish to emphasize that this is an important feature of dependent type theory: | |
| every term has a computational behavior, and supports a notion of reduction, or *normalization*. | |
| In principle, two terms that reduce to the same value are called *definitionally equal*. | |
| They are considered "the same" by Lean's type checker, and Lean does its best to recognize and support these identifications. | |
| The `#reduce` command is mainly useful to understand why two terms are considered the same. | |
| Lean is also a programming language. It has a compiler to native code and an interpreter. | |
| You can use the command `#eval` to execute expressions, and it is the preferred way of testing your functions. | |
| Note that `#eval` and `#reduce` are *not* equivalent. The command `#eval` first compiles Lean expressions | |
| into an intermediate representation (IR) and then uses an interpreter to execute the generated IR. | |
| Some builtin types (e.g., `Nat`, `String`, `Array`) have a more efficient representation in the IR. | |
| The IR has support for using foreign functions that are opaque to Lean. | |
| In contrast, the ``#reduce`` command relies on a reduction engine similar to the one used in Lean's trusted kernel, | |
| the part of Lean that is responsible for checking and verifying the correctness of expressions and proofs. | |
| It is less efficient than ``#eval``, and treats all foreign functions as opaque constants. | |
| We later discuss other differences between the two commands. |