Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do java lambda expressions not introduce a new level of scope?

As I understand, in languages such as Haskell, and also as part of the lambda calculus, each lambda expression has its own scope, so if I have nested lambda expressions such as: \x -> (\x -> x) then the first \x parameter is different to the second \x.

In Java if you do this you get a compilation error, just like if you use x again as the parameter name or a local variable name within the lambda if it has already been used inside the enclosing scope, e.g. as a method parameter.

Does anybody know why Java implemented lambda expressions this way - why not have them introduce a new level of scope and behave like an anonymous class would? I'm assuming it's because of some limitation or optimisation, or possibly because lambdas had to be hacked into the existing language?

like image 881
Tranquility Avatar asked Apr 29 '16 15:04

Tranquility


People also ask

What is disadvantages of lambda expression in Java?

Maybe, the main disadvantage is that Lambda expressions can only be used with functional interfaces, ad not other “more object oriented” classes. Other con, but not for all, is that it can seem some confused for programmers used to model everything in an object-oriented style.

What is the scope of lambda expression in Java?

A lambda expression does not define a scope of its own. If we use keywords this and super in lambda expression inside a method, they act the same as we use them in that method.

Can a lambda expression shadow a variable that existed in the enclosing scope?

Lambda parameters are not allowed to shadow variables in the enclosing scopes.

Why do we introduce lambda expressions in Java?

Introduction. Lambda expressions are a new and important feature included in Java SE 8. They provide a clear and concise way to represent one method interface using an expression. Lambda expressions also improve the Collection libraries making it easier to iterate through, filter, and extract data from a Collection .


2 Answers

A lambda block is a new block, aka scope, but it does not establish a new context/level, like an anonymous class implementation does.

From Java Language Specification 15.27.2 Lambda Body:

Unlike code appearing in anonymous class declarations, the meaning of names and the this and super keywords appearing in a lambda body, along with the accessibility of referenced declarations, are the same as in the surrounding context (except that lambda parameters introduce new names).

And from JLS 6.4 Shadowing and Obscuring:

These rules allow redeclaration of a variable or local class in nested class declarations (local classes (§14.3) and anonymous classes (§15.9)) that occur in the scope of the variable or local class. Thus, the declaration of a formal parameter, local variable, or local class may be shadowed in a class declaration nested within a method, constructor, or lambda expression; and the declaration of an exception parameter may be shadowed inside a class declaration nested within the Block of the catch clause.

There are two design alternatives for handling name clashes created by lambda parameters and other variables declared in lambda expressions. One is to mimic class declarations: like local classes, lambda expressions introduce a new "level" for names, and all variable names outside the expression can be redeclared. Another is a "local" strategy: like catch clauses, for loops, and blocks, lambda expressions operate at the same "level" as the enclosing context, and local variables outside the expression cannot be shadowed. The above rules use the local strategy; there is no special dispensation that allows a variable declared in a lambda expression to shadow a variable declared in an enclosing method.

Example:

class Test {
    private int f;
    public void test() {
        int a;
        a = this.f;     // VALID
        {
            int a;      // ERROR: Duplicate local variable a
            a = this.f; // VALID
        }
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                int a;           // VALID (new context)
                a = this.f;      // ERROR: f cannot be resolved or is not a field
                                 //   (this refers to the instance of Runnable)
                a = Test.this.f; // VALID
            }
        };
        Runnable r2 = () -> {
            int a;      // ERROR: Lambda expression's local variable a cannot redeclare another local variable defined in an enclosing scope.
            a = this.f; // VALID
        };
    }
}
like image 53
Andreas Avatar answered Nov 11 '22 12:11

Andreas


Lambdas in Java do introduce a new scope - any variable declared in a lambda is only accessible within the lambda.

What you really ask about is shadowing - changing binding of a variable already bound in some outer scope.

It is logical to allow some level of shadowing: you want to be able to shadow global names by local names, because otherwise you can break local code just by adding a new name to some global namespace. A lot of langues, for sake of simplicity, simply extend this rule down to local names.

On the other hand, rebinding local names is a code smell and can be a source of subtle mistakes, while - at the same time - not offering any technical advantage. Since you mentioned Haskell, you can look at this discussion on Lambda the Ultimate.

This is why Java disallows shadowing of local variables (like many other potentially dangerous things), but allows shadowing attributes by local variables (so that adding attributes will never break a method that already used the name).

So, the designers of Java 8 had to answer a question if lambdas should behave more like code blocks (no shadowing) or like inner classes (shadowing) and made a conscious decision to treat them like the former.

like image 35
fdreger Avatar answered Nov 11 '22 11:11

fdreger