views:

850

answers:

4

I just ran into this while going through the Erlang compiler source.

I'm not really getting it. (go figure ;)), considering that I just realized that there is such a thing 5 minutes ago).

Forgive me for asking first without first trying to understand reasons for its existence.

There is a wikipedia article about it, but it is pretty cryptic.

+12  A: 

Lambda lifting is used to turn a closure into a pure function. By passing extra arguments to the function, you reduce the number of its free variables. As you "lift" the lambda into higher and higher scopes, you add arguments to accommodate the local variables declared in that scope (which would be free variables otherwise). Once the lambda has no free variables it is a pure "top level" function.

Of course you can only do this if you know all the lambda's call sites; in other words, only if the lambda does not escape.

The benefit in a compiler optimizer is that closures (function environments) can be eliminated. This might make it possible to pass the arguments in registers rather than stack (or heap) allocating them as free variables.

Doug Currie
so a free variable in closures sense is one thats coming from its lexical environment (scope of its definition?). a top level function is (in erlangs case) a module function?what about a unbound variable thats already in a module level function, is it free as well?thanks for the detailed answer
deepblue
Yes, yes, maybe.
Doug Currie
Just got why it speeds things up,realized it after the simple JS example.You brought it up too.Im still not getting why you have to know all lambda's call sites in order to perform the lifting... shouldnt only the lex env in which it was defined matter (since this is where the free vars come from)?
deepblue
The reason you need to know all call sites is because you need to update all calls to the function by adding the extra parameter (x in my JavaScript example).
Tom Lokhorst
ahhhaaa!!! :)) thank you. what a great feeling when you learn something new :). so is this how closures are implemented? or do you need support from the VM as well?
deepblue
This is how closures are implemented if you can determine all the call sites of the function; if you can't, then you need to construct an environment that holds all the free variables of the function. It may be on the stack (if the function only escapes down) or on the heap otherwise.
Doug Currie
what do you mean by "escapes down"? what would be the other case?
deepblue
Escapes down means it is passed to a known function, and that function doesn't let it escape up. So, the lifetime of the closure is smaller than the lifetime of the enclosing function, so it can be stack allocated.
Doug Currie
Escapes up means the closure outlives the environment in which is is created. It could be stored in a data structure that escapes up, or it could be passed to a function that may let it escape, or it could be returned by the function that creates it. So, the environment must be on the heap.
Doug Currie
A: 

lambda lifting basically eliminates variables and puts them into pure functions, simplifying the execution.

Jweede
why does that simplify execution?sorry for the blunt question.Wikipedia article mentioned the same thing, namely that the purpose of lifting is to speed things up.every little thing points me in the right direction. thanks
deepblue
+14  A: 

Lambda lifting is a technique to `lift' lambdas to a higher level (mostly to the top level).

Doug Currie describes why you would want to do this.

Here's some example code (in JavaScript) of how you could do this manually:

function addFive(nr)
{
  var x = 5;
  function addX(y)
  {
    return x + y;
  }

  return addX(nr);
}

Now if you don't want this addX function inside the definition of addFive you could `lift' it to the top level like so:

function addX(y)
{
  return x + y;
}

function addFive(nr)
{
  var x = 5;

  return addX(nr);
}

However, this won't work, since the x variable isn't available anymore in the context of the addX function. The way to fix this is to add an extra formal parameter to the function:

function addX(y, x)
{
  return x + y;
}

function addFive(nr)
{
  var x = 5;

  return addX(nr, x);
}


Addition: Here's a very contrived example of a lambda `escaping'. Where you won't be able to do the lambda lifting as easily as I've described.

function getAddFiveFunc()
{
  var x = 5;
  function addX(y)
  {
    return x + y;
  }

  return addX;
}

Now if someone calls the getAddFiveFunc function, they will get a function back. This function could be used at all sorts of places, Now, if you do want to lift the addX function, you will have to update all those callsites.

Tom Lokhorst
very simple example, I get it now:) thank you. so a free variable is considered to be one thats "pulled" from the surrounding lexical environment of the closure? aaaah, so the way lifting speeds things up is that you no longer have to carry the lexical environment for every closure?
deepblue
I was just going to ask you about what happens when the closure is returned from its container function.so say I still want to lift it, how will I expose the local vars of the container func to the outside callers of the closure(in order to pass them as parameters)?
deepblue
If your compiler has access to the complete program at compile-time, it will be able to update all call sites. However, most compilers allow for separate compilation, where each module is compiled separately. In that case, if the lambda crosses module boundaries, lambda lifting won't be possible.
Tom Lokhorst
understood.it makes sense that erlang wouldnt lift accross module boundaries since it allows for dynamic reloading of modules without needing to recompile the rest, and as long as the exported function signatures stay the same the rest can be changed.thanks for the good examples
deepblue
+1  A: 

Warning: My answer actually describes captured variables which is different than lambda lifting. Misread the question (need sleep). But I spent a bit of time writing this up so I'm loath to delete it. Left it up as a community WIKI.

Lambda lifting, often referred to as closures, is a way of seamlessly allowing access of in scope variables from within a nested lambda expression.

It's hard to get into the nitty gritty details of closures without picking a particular language. One of the side effects of lambda lifting, in any langauge, is that it tends to extend the lifetime of a variable from a local, short lived scope, to a much longer lived scope. Usually this occurs in the form of transferring a variable from the stack to the heap within the compiler. This is a very language specific action and therefore produces very different implementations based on the language.

I'll focus on C# since that's probably the language most common to the readers of stack overflow. Lets start with the following code.

public Func<int> GetAFunction() {
  var x = 42;
  Func<int> lambda1 = () => x;
  Func<int> lambda2 = () => 42;
  ...
  return lambda1;
}

In this example we've created 2 lambda expressions. In both cases it's assigned to a delegate instance of type Func. All delegates in .Net require that a real function be backing them somewhere. So under the hood, all lambda expressions/ anonymous functions in C# are translated into a method definition.

Generating a function for lambda2 is pretty straight forward. It's an isolated function that just returns a constant value.

public static int RealLambda2() { 
  return 42;
}

Generating lambda1 is quite a bit harder. A literal definition would look like the following

public static int RealLambda1() {
  return x;
}

This obviously won't compile because x is not accessible. In order to make this work, the C# compiler must lift the variable x into a closure. It can then return a pointer to a function within the closure to satisfy the delegate expression

class Closure1 {
  int x;
  public int RealLambda1() {
    return x;
  }
}

This is a pretty simple example but should hopefully detail the art of lifting. The devil is unfortunately in the details and gets much more complex with the scenario.

JaredPar
You are talking about captured variables, not lambda lifting.
leppie
@leppie, you're correct, I need to get some sleep
JaredPar