Lambda lifting is a
meta-process that restructures a
computer program
A computer program is a sequence or set of instructions in a programming language for a computer to Execution (computing), execute. It is one component of software, which also includes software documentation, documentation and other intangibl ...
so that
functions are defined independently of each other in a global
scope. An individual ''lift'' transforms a local function (subroutine) into a global function. It is a two step process, consisting of:
* Eliminating
free variables in the function by adding
parameters.
* Moving functions from a restricted scope to broader or global scope.
The term "lambda lifting" was first introduced by Thomas Johnsson around 1982 and was historically considered as a mechanism for implementing
programming language
A programming language is a system of notation for writing computer programs.
Programming languages are described in terms of their Syntax (programming languages), syntax (form) and semantics (computer science), semantics (meaning), usually def ...
s based on
functional programming
In computer science, functional programming is a programming paradigm where programs are constructed by Function application, applying and Function composition (computer science), composing Function (computer science), functions. It is a declarat ...
. It is used in conjunction with other techniques in some modern
compiler
In computing, a compiler is a computer program that Translator (computing), translates computer code written in one programming language (the ''source'' language) into another language (the ''target'' language). The name "compiler" is primaril ...
s.
Lambda lifting is not the same as closure conversion. It requires all
call sites to be adjusted (adding extra arguments (parameters) to calls) and does not introduce a
closure for the lifted lambda expression. In contrast, closure conversion does not require call sites to be adjusted but does introduce a closure for the lambda expression mapping free variables to values.
The technique may be used on individual functions, in
code refactoring
In computer programming and software design, code refactoring is the process of restructuring existing source code—changing the '' factoring''—without changing its external behavior. Refactoring is intended to improve the design, structure, ...
, to make a function usable outside the scope in which it was written. Lambda lifts may also be repeated, to transform the program. Repeated lifts may be used to convert a program written in
lambda calculus
In mathematical logic, the lambda calculus (also written as ''λ''-calculus) is a formal system for expressing computability, computation based on function Abstraction (computer science), abstraction and function application, application using var ...
into a set of
recursive functions, without lambdas. This demonstrates the equivalence of programs written in lambda calculus and programs written as functions. However it does not demonstrate the soundness of lambda calculus for deduction, as the
eta reduction used in lambda lifting is the step that introduces
cardinality problems into the lambda calculus, because it removes the value from the variable, without first checking that there is only one value that satisfies the conditions on the variable (see
Curry's paradox).
Lambda lifting is expensive on processing time for the compiler. An efficient implementation of lambda lifting is
on processing time for the compiler.
[
]
In the
untyped lambda calculus, where the basic types are functions, lifting may change the result of
beta reduction of a lambda expression. The resulting functions will have the same meaning, in a mathematical sense, but are not regarded as the same function in the untyped lambda calculus. See also
intensional versus extensional equality.
The reverse operation to lambda lifting is
lambda dropping.
Lambda dropping may make the compilation of programs quicker for the compiler, and may also increase the efficiency of the resulting program, by reducing the number of parameters, and reducing the size of stack frames.
However it makes a function harder to re-use. A dropped function is tied to its context, and can only be used in a different context if it is first lifted.
Algorithm
The following algorithm is one way to lambda-lift an arbitrary program in a language which doesn't support closures as
first-class objects:
# Rename the functions so that each function has a unique name.
# Replace each free variable with an additional argument to the enclosing function, and pass that argument to every use of the function.
# Replace every local function definition that has no free variables with an identical global function.
# Repeat steps 2 and 3 until all free variables and local functions are eliminated.
If the language has closures as first-class objects that can be passed as arguments or returned from other functions, the closure will need to be represented by a data structure that captures the bindings of the free variables.
Example
The following
OCaml
OCaml ( , formerly Objective Caml) is a General-purpose programming language, general-purpose, High-level programming language, high-level, Comparison of multi-paradigm programming languages, multi-paradigm programming language which extends the ...
program computes the sum of the integers from 1 to 100:
let rec sum n =
if n = 1 then
1
else
let f x = n + x in
f (sum (n - 1))
sum 100
(The
let rec
declares
sum
as a function that may call itself.) The function f, which adds sum's argument to the sum of the numbers less than the argument, is a local function. Within the definition of f, n is a free variable. Start by converting the free variable to a parameter:
let rec sum n =
if n = 1 then
1
else
let f w x =
w + x in
f n (sum (n - 1))
sum 100
Next, lift f into a global function:
let rec f w x =
w + x
and sum n =
if n = 1 then
1
else
f n (sum (n - 1)) in
sum 100
The following is the same example, this time written in
JavaScript
JavaScript (), often abbreviated as JS, is a programming language and core technology of the World Wide Web, alongside HTML and CSS. Ninety-nine percent of websites use JavaScript on the client side for webpage behavior.
Web browsers have ...
:
// Initial version
function sum(n)
// After converting the free variable n to a formal parameter w
function sum(n)
// After lifting function f into the global scope
function f(w, x)
function sum(n)
Lambda lifting versus closures
Lambda lifting and
closure are both methods for implementing
block structured programs. It implements block structure by eliminating it. All functions are lifted to the global level. Closure conversion provides a "closure" which links the current frame to other frames. Closure conversion takes less compile time.
Recursive functions, and block structured programs, with or without lifting, may be implemented using a
stack based implementation, which is simple and efficient. However a stack frame based implementation must be
strict (eager). The stack frame based implementation requires that the life of functions be
last-in, first-out (LIFO). That is, the most recent function to start its calculation must be the first to end.
Some functional languages (such as
Haskell
Haskell () is a general-purpose, statically typed, purely functional programming language with type inference and lazy evaluation. Designed for teaching, research, and industrial applications, Haskell pioneered several programming language ...
) are implemented using
lazy evaluation
In programming language theory, lazy evaluation, or call-by-need, is an evaluation strategy which delays the evaluation of an Expression (computer science), expression until its value is needed (non-strict evaluation) and which avoids repeated eva ...
, which delays calculation until the value is needed. The lazy implementation strategy gives flexibility to the programmer. Lazy evaluation requires delaying the call to a function until a request is made to the value calculated by the function. One implementation is to record a reference to a "frame" of data describing the calculation, in place of the value. Later when the value is required, the frame is used to calculate the value, just in time for when it is needed. The calculated value then replaces the reference.
The "frame" is similar to a
stack frame, the difference being that it is not stored on the stack. Lazy evaluation requires that all the data required for the calculation be saved in the frame. If the function is "lifted", then the frame needs only record the
function pointer
A function pointer, also called a subroutine pointer or procedure pointer, is a pointer referencing executable code, rather than data. Dereferencing the function pointer yields the referenced function, which can be invoked and passed arguments ...
, and the parameters to the function. Some modern languages use
garbage collection in place of stack based allocation to manage the life of variables. In a managed, garbage collected environment, a
closure records references to the frames from which values may be obtained. In contrast a lifted function has parameters for each value needed in the calculation.
Let expressions and lambda calculus
The
Let expression is useful in describing lifting, dropping, and the relationship between recursive equations and lambda expressions. Most functional languages have let expressions. Also, block structured programming languages like
ALGOL
ALGOL (; short for "Algorithmic Language") is a family of imperative computer programming languages originally developed in 1958. ALGOL heavily influenced many other languages and was the standard method for algorithm description used by the ...
and
Pascal are similar in that they too allow the local definition of a function for use in a restricted
scope.
The ''let'' expression used here is a fully mutually recursive version of ''let rec'', as implemented in many functional languages.
Let expressions are related to
Lambda calculus
In mathematical logic, the lambda calculus (also written as ''λ''-calculus) is a formal system for expressing computability, computation based on function Abstraction (computer science), abstraction and function application, application using var ...
. Lambda calculus has a simple syntax and semantics, and is good for describing Lambda lifting. It is convenient to describe lambda lifting as a translations from ''lambda'' to a ''let'' expression, and lambda dropping as the reverse. This is because ''let'' expressions allow mutual recursion, which is, in a sense, more lifted than is supported in lambda calculus. Lambda calculus does not support mutual recursion and only one function may be defined at the outermost global scope.
Conversion rules which describe translation without lifting are given in the
Let expression article.
The following rules describe the equivalence of lambda and let expressions,
Meta-functions will be given that describe lambda lifting and dropping. A meta-function is a function that takes a program as a parameter. The program is data for the meta-program. The program and the meta program are at different meta-levels.
The following conventions will be used to distinguish program from the meta program,
* Square brackets [] will be used to represent function application in the meta program.
* Capital letters will be used for variables in the meta program. Lower case letters represent variables in the program.
*
will be used for equals in the meta program.
*
represents a dummy variable, or an unknown value.
For simplicity the first rule given that matches will be applied. The rules also assume that the lambda expressions have been pre-processed so that each lambda abstraction has a unique name.
The substitution operator is used extensively. The expression
means substitute every occurrence of ''G'' in ''L'' by ''S'' and return the expression. The definition used is extended to cover the substitution of expressions, from the definition given on the
Lambda calculus
In mathematical logic, the lambda calculus (also written as ''λ''-calculus) is a formal system for expressing computability, computation based on function Abstraction (computer science), abstraction and function application, application using var ...
page. The matching of expressions should compare expressions for alpha equivalence (renaming of variables).
Lambda lifting in lambda calculus
Each lambda lift takes a lambda abstraction which is a sub expression of a lambda expression and replaces it by a function call (application) to a function that it creates. The free variables in the sub expression are the parameters to the function call.
Lambda lifts may be used on individual functions, in
code refactoring
In computer programming and software design, code refactoring is the process of restructuring existing source code—changing the '' factoring''—without changing its external behavior. Refactoring is intended to improve the design, structure, ...
, to make a function usable outside the scope in which it was written. Such lifts may also be repeated, until the expression has no lambda abstractions, to transform the program.
Lambda lift
A lift is given a sub-expression within an expression to lift to the top of that expression. The expression may be part of a larger program. This allows control of where the sub-expression is lifted to. The lambda lift operation used to perform a lift within a program is,
:
The sub expression may be either a lambda abstraction, or a lambda abstraction applied to a parameter.
Two types of lift are possible.
*
Anonymous lift
* #Named lift">Named lift
An anonymous lift has a lift expression which is a lambda abstraction only. It is regarded as defining an anonymous function. A name must be created for the function.
A named lift expression has a lambda abstraction applied to an expression. This lift is regarded as a named definition of a function.
Anonymous lift
An anonymous lift takes a lambda abstraction (called ''S''). For ''S'';
* Create a name for the function that will replace ''S'' (called ''V''). Make sure that the name identified by ''V'' has not been used.
* Add parameters to ''V'', for all the free variables in ''S'', to create an expression ''G'' (see ''make-call'').
The lambda lift is the substitution of the lambda abstraction ''S'' for a function application, along with the addition of a definition for the function.
:
The new lambda expression has ''S'' substituted for G: ''L''[''S'':=''G''] means substitution of ''S'' for ''G'' in ''L''. The function definitions has the function definition ''G = S'' added.
In the above rule ''G'' is the function application that is substituted for the expression ''S''. It is defined by,
:
where ''V'' is the function name. It must be a new variable, i.e. a name not already used in the lambda expression,
:
where
is a meta function that returns the set of variables used in ''E''.
= Constructing the call
=
The function call ''G'' is constructed by adding parameters for each variable in the free variable set (represented by ''V''), to the function ''H'',
*
*
Named lift
The named lift is similar to the anonymous lift except that the function name ''V'' is provided.
:
As for the anonymous lift, the expression ''G'' is constructed from ''V'' by applying the free variables of ''S''. It is defined by,
:
Lambda-lift transformation
A lambda lift transformation takes a lambda expression and lifts all lambda abstractions to the top of the expression. The abstractions are then translated into
recursive functions, which eliminates the lambda abstractions. The result is a functional program in the form,
*
where ''M'' is a series of function definitions, and ''N'' is the expression representing the value returned.
For example,
:
The ''de-let'' meta function may then be used to convert the result back into lambda calculus.
:
The processing of transforming the lambda expression is a series of lifts. Each lift has,
* A sub expression chosen for it by the function ''lift-choice''. The sub expression should be chosen so that it may be converted into an equation with no lambdas.
* The lift is performed by a call to the ''lambda-lift'' meta function, described in the next section,
:
After the lifts are applied the lets are combined together into a single let.
:
Then
Parameter dropping is applied to remove parameters that are not necessary in the "let" expression. The let expression allows the function definitions to refer to each other directly, whereas lambda abstractions are strictly hierarchical, and a function may not directly refer to itself.
Choosing the expression for lifting
There are two different ways that an expression may be selected for lifting. The first treats all lambda abstractions as defining anonymous functions. The second, treats lambda abstractions which are applied to a parameter as defining a function. Lambda abstractions applied to a parameter have a dual interpretation as either a let expression defining a function, or as defining an anonymous function. Both interpretations are valid.
These two predicates are needed for both definitions.
''lambda-free'' - An expression containing no lambda abstractions.
:
''lambda-anon'' - An anonymous function. An expression like
where X is lambda free.
:
= Choosing anonymous functions only for lifting
=
Search for the deepest anonymous abstraction, so that when the lift is applied the function lifted will become a simple equation. This definition does not recognize a lambda abstractions with a parameter as defining a function. All lambda abstractions are regarded as defining anonymous functions.
''lift-choice'' - The first anonymous found in traversing the expression or ''none'' if there is no function.
#
#