30

Assume we have an Iterator<Integer> iterator. Because Iterable is a functional interface, we can write:

Iterable<Integer> iterable = () -> iterator;

We can then of course use iterable as an enhanced for loop's Expression:

for (Integer i : iterable) foo(i);

So why is

for (Integer i : () -> iterator) foo(i);

not allowed? (It results in the following compiler error:)

error: lambda expression not expected here
    for (Integer i : () -> iterator) foo(i);
                     ^

Making the target type explicit like

for (Integer i : (Iterable<Integer>) () -> iterator) foo(i);

obviously works, but why can the compiler not infer the λ-expression's target type if it is omitted? From the fact that the Expression is in λ notation, should it not be clear to the compiler that the target type cannot be an Array, and hence must be Iterable?

Is this just an oversight by the language designers, or is there something else I am missing here?

user4235730
  • 2,559
  • 1
  • 23
  • 36
  • I don't see [Iterable](https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html) marked as `@FunctionalInterface` – Farrandu Sep 10 '15 at 16:13
  • 2
    @Farrandu it doesn't need to be marked as FunctionalInterface for it to be a functional interface – Sleiman Jneidi Sep 10 '15 at 16:15
  • @SleimanJneidi Ooops... you're right, didn't know that – Farrandu Sep 10 '15 at 16:17
  • 7
    @Farrandu It doesn't have to be. [JLS 9.8](https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.8) says `A functional interface is an interface that has just one abstract method (aside from the methods of Object), and thus represents a single function contract.` The `@FunctionalInterface` clarifies that it is intended to be used as a functional interface, and is a compile time error if it is not. – mkobit Sep 10 '15 at 16:17
  • @Pshermo: thank you for that edit. I originally put the λ in there to find out whether SO's search functionality resolves the alias. It does not. – user4235730 Sep 10 '15 at 16:25
  • 2
    It's not marked as `@FunctionalInterface` because it's not particularly intended to be used this way. – Louis Wasserman Sep 10 '15 at 17:26

3 Answers3

17

This is not just about lambda expression; it's about all poly expressions that require target typing.

One thing for sure is that this is not an oversight; the case was considered and rejected.

To quote an early spec :

http://cr.openjdk.java.net/~dlsmith/jsr335-0.9.3/D.html

Deciding what contexts are allowed to support poly expressions is driven in large part by the practical need for such features:

The expression in an enhanced for loop is not in a poly context because, as the construct is currently defined, it is as if the expression were a receiver: exp.iterator() (or, in the array case, exp[i]). It is plausible that an Iterator could be wrapped as an Iterable in a for loop via a lambda expression (for (String s : () -> stringIterator)), but this doesn't mesh very well with the semantics of Iterable.

My take is that, each invocation of Iterable.iterator() must return a new, independent iterator, positioned at the beginning. Yet, the lambda expression in the example (and in your example) returns the same iterator. This does not conform to the semantics of Iterable.


In any case, it seems unnecessary work to support target typing in for-each loop. If you already have the iterator, you can simply do

    iterator.forEachRemaining( element->{ ... } )

Or if you prefer old-school

    while(iterator.hasNext()) {
        Foo elment = iterator.next();

Neither are too bad; it's not worth complicating the language spec even more. (If we do want for-each to provide target typing, remember it needs to work for other poly expressions as well, like ?:; then for-each can become too difficult to understand in some cases. And in general, there are two possible target types, Iterable<? extends X> | X[], which is very difficult for the type inference system.)


The for-each construct could be considered a syntax sugar because lambda wasn't available. If the language already has lambda expression, it is really unnecessary to have a special language construct to support for-each; it can be done by library APIs.

Community
  • 1
  • 1
ZhongYu
  • 19,446
  • 5
  • 33
  • 61
  • Thank you that link, it was very helpful. I was not aware of the concept (name) of „poly expressions“, and therefore have been looking in wrong places. The way I read this is „yeah we could implement that but why bother, it's not that useful“. However I don't get how it „doesn't mesh very well with the semantics of Iterable“. Anyway, this answers my question, so I'm going to accept this. – user4235730 Sep 10 '15 at 17:01
  • 1
    yeah, that draft spec is a lot more readable than digging through JLS :) – ZhongYu Sep 10 '15 at 17:03
  • However, I think that `forEachRemaining` is not always sufficient. For instance, you cannot `break` or `return` from its body. And `while` sometimes forces you to introduce a binding, so you can't simply do something along the lines of `for (Element e : () -> query.find())` where `find` is some expensive operation that returns an `Iterator`; with `while` you would have to write `Results r = query.getResults(); while(r.hasNext()) ...`. – user4235730 Sep 10 '15 at 17:14
  • 1
    @user4235730 - you are right. It is simpler to force an Iterable in such cases. It's up to the programmer to reason that it is safe to do so. – ZhongYu Sep 10 '15 at 17:17
  • 1
    @user4235730 - we can provide a helper method for establishing target typing, resulting in simpler syntax like `for(Element e : iter(query::find))`. see also http://stackoverflow.com/a/32207355/2158288 – ZhongYu Sep 10 '15 at 17:21
  • 2
    @user4235730: The point about the semantics of Iterable are discussed [here](http://stackoverflow.com/q/839178/2711488) and [here](http://stackoverflow.com/q/3883131/2711488). The discussion is much older than lambda expressions… – Holger Sep 10 '15 at 17:29
  • 1
    @bayou.io: Actually, there is no problem in telling the target types `Iterable extends X>` and `X[]` apart. You may try it with overloaded methods. The compiler knows that a lambda expression can never resolve to an array type… – Holger Sep 10 '15 at 17:34
  • 1
    @Holger - you are right that it is possible. But overload resolution is highly complicated. Anyways, let's just say the language designer didn't have the energy or patience to rewrite for-each spec :) – ZhongYu Sep 10 '15 at 17:41
  • 2
    @bayou.io: Well, I’m happy with everything you have written before the last edit, saying that the language designer didn't *want* to enable it for the foreach loop. Hence, the difficulties of the type inference are irrelevant here… – Holger Sep 10 '15 at 17:44
  • 1
    @Holger - I do know that they were under stress, and they were happy if they didn't *have to* do something :). Even given unlimited resource and time, it is still dubious whether this feature is desired. I still haven't see legit use cases. It is highly unusual that an Iterator is created by a non-Iterable interface. The `query.find()->Iterator` example seems a bad API design to start with. It should probably be `query.results()->Iterable` instead. – ZhongYu Sep 10 '15 at 17:58
  • 3
    @Holger The main difficulty with the enhanced-for loop is that it can't be analyzed until the type of expression on the RHS of the colon is known; but if that expression is a lambda, its type can't be determined without a target type, which doesn't exist yet because the for-loop analysis hasn't been done. As you observe a lambda can't result in an array, so "it's plausible" this could be done by speculatively establishing Iterable as the target type. But by and large javac avoids speculative type inference; that way leads to madness. – Stuart Marks Sep 10 '15 at 20:55
  • 3
    @Holger The above, combined with the dubious semantics of having a lambda produce an Iterable, pushed this one very far down the priority list. Extra effort to handle this single case specially, combined with low benefit, essentially explains "the language designers / compiler writers didn't want to do it." It's likely that if the value of case were considered to be very high, some way would have been found to make it work. – Stuart Marks Sep 10 '15 at 20:57
  • 1
    @bayou.io Tagging you, as the above comments are intended for you as well. – Stuart Marks Sep 10 '15 at 20:58
  • 1
    @StuartMarks - we could even say that this design is of negative value - what the hell is `for(Foo foo : ()->{...})` ? It's hard to understand intuitively. `Iterable` is not exactly functional-ish, so it will be very confusing if it's created from a lambda expression. – ZhongYu Sep 10 '15 at 22:45
  • @Stuart Marks: that’s how I understood the issue. “the language designer didn’t want to” wasn’t meant to be an offense, it was just meant as a differentiation against the theory “they were incapable of doing it”. Just emphasizing the deliberate decision aspect… – Holger Sep 11 '15 at 08:41
  • 1
    Or, as a final remark, everything could be so simple if just `Iterable` had a second `abstract` method… – Holger Sep 11 '15 at 08:42
7

In the Lambda Expressions documentation, they list the scenarios in which the target type can be inferred, specifically:

To determine the type of a lambda expression, the Java compiler uses the target type of the context or situation in which the lambda expression was found. It follows that you can only use lambda expressions in situations in which the Java compiler can determine a target type:

  • Variable declarations

  • Assignments

  • Return statements

  • Array initializers

  • Method or constructor arguments

  • Lambda expression bodies

  • Conditional expressions, ?:

  • Cast expressions

This scenario is none of these, so the target type cannot be inferred. I agree with you that the target type can never be an array (because an array is not a functional interface), but the documentation is clear that this is not one of these scenarios.

Specifically, in the JLS 15.27.3, it says:

A lambda expression is compatible in an assignment context, invocation context, or casting context with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of the ground target type derived from T.

  • Assignment context - Assignment contexts allow the value of an expression to be assigned (§15.26) to a variable.
  • Invocation context - Invocation contexts allow an argument value in a method or constructor invocation
  • Casting context - Casting contexts allow the operand of a cast operator (§15.16) to be converted to the type explicitly named by the cast operator.

Clearly, this is none of these. The only one that's even possible is an Invocation context, but the enhanced for loop construct is not a method invocation.


As far as "why" this scenario is not allowed by the Java authors, I have no idea. Speaking to the mind of the writers of Java is generally outside the scope of Stack Overflow; I have attempted to explain why the code doesn't work, but I can't guess why they chose to write it this way.

Addendum, the explanation / discussion in @bayou.io's answer is still present in the final version of JSR-335:

Lambda expressions and method references may only appear in certain contexts, and their type and correctness are determined by this context. Other kinds of expressions in the existing language have already introduced dependencies on context, and this is a trend that seems likely to continue. Rather than treat each new feature in an ad-hoc manner, the introduction of poly expression and an explicit recognition that target types can influence expression types allows us to unify handling of context-dependent expressions under a single umbrella.

... snip

The expression in an enhanced for loop is not in a poly context because, as the construct is currently defined, it is as if the expression were a receiver: exp.iterator() (or, in the array case, exp[i]). It is plausible that an Iterator could be wrapped as an Iterable in a for loop via a lambda expression (for (String s : () -> stringIterator)), but this doesn't mesh very well with the semantics of Iterable.

Community
  • 1
  • 1
durron597
  • 31,968
  • 17
  • 99
  • 158
-1

As durron597 explains you can only use a lambda expression in situations in which the Java compiler can determine a target type.

In a for-each loop :

for ( FormalParameter : Expression ) Statement

The JLS says that :

The type of the Expression must be Iterable or an array type (§10.1), or a compile-time error occurs.

Which means two different types are allowed, not one as required for a lambda expression, thus there was no way to design the language in a way that lambda expressions would work in for-each loops. Not with the concept of lambda expression used.

When you add a cast then only one type is allowed, thus the compiler can determine a target type for the lambda expression. Which is why "Making the target type explicit" works.

Community
  • 1
  • 1
Anonymous Coward
  • 3,140
  • 22
  • 39
  • „It would also work if you had an array instead of an Iterator and used a lambda expression with a cast to the proper array type.“ ... could you please give an example for that? – user4235730 Sep 10 '15 at 17:22
  • 1
    @JoseAntonioDuraOlmos Why are you saying that there was "no way to do it", when the author of JSR-335 says it's "plausible" it could have been done that way, but it just didn't fit with `Iterable`s semantics, as it states in both my an bayou.io's answers? – durron597 Sep 10 '15 at 19:47
  • Current lambda expressions need to be in a context where one functional interface is expected. That happens in assignments, invocation and casting. In the expression of a for-each two candidates are expected, an Iterable (which happens to be a functional interface) or a char[]. It would have been possible with slightly different lambda expressions, ones allowed when several expected types are allowed but only one of them is a functional interface. So, yes, it could be done with a different concept of lambda but not with the current one. They likely felt the extra complexity was not worth it. – Anonymous Coward Sep 11 '15 at 11:28
  • and Iterable or an array I meant, any array. Needs not be char[] – Anonymous Coward Sep 11 '15 at 11:34
  • That's not what you said though. You said "no way to do it", you didn't say "extra complexity is not worth it". – durron597 Sep 11 '15 at 16:58
  • Actually I said "no way to do it.... Not with the concept of lambda expression used". I do not deny that it can be done. I deny that it can be done without changing the concept of lambda used. In particular regarding the need of a single target type. The "extra complexity is not worth it" is speculation (as implied by "likely") about how some decisions were made and may be wrong; but it needs not be right for the answer to be right. Even if it had been worth it, for-each lambdas would not have been possible without changing the concept of lambda used. – Anonymous Coward Sep 11 '15 at 18:07