In a lambda-lifted program, all functions are defined at the toplevel. For example, consider the following definition of the addition function:
Fixpoint add_lifted (n1 n2 : nat) : nat :=
match n1 with
O =>
n2
| S n1' =>
S (add_lifted n1' n2)
end.
In contrast, a lambda-dropped program has a block structure and local definitions, and it actively uses lexical scope:
Definition add_dropped (n1 n2 : nat) : nat :=
let fix visit i :=
match i with
O =>
n2
| S i' =>
S (visit i')
end
in visit n1.
We only have considered lambda-lifted programs so far. Accordingly, all our theorems and lemmas are defined at the toplevel, which means that our proofs are lambda-lifted too.
Does it mean that we cannot reason about lambda-dropped programs? The answer to this rhetorical question is no.
In the first accompanying file, the equivalence of add_lifted and of add_dropped is proved. The two fold-unfold lemmas corresponding to add_lifted are declared globally, and the two fold-unfold lemmas corresponding to visit are declared locally. The rest of the proof is a routine induction over n1.
In the second accompanying file, two main functions are implemented with a local auxiliary recursive function. Reasoning about these main functions entails declaring local auxiliary lemmas to reason about their local auxiliary function. Due to their locality, these auxiliary lemmas are duplicated in other proofs. In contrast, these auxiliary lemmas are shared when reasoning about lambda-lifted programs, just as identical local auxiliary functions in a lambda-dropped program are shared once this program is lambda-lifted.
In the rest of the semester, we will continue to only consider lambda-lifted programs.
Factored out of another chapter and fleshed out [20 Aug 2024]