why have the modified if position in scala?
So I'm learning Scala and came across some examples like this:
val doubleEven = for (i <- 1 to 10; if i % 2 == 0)
yield i * 2
Now, what's the added benefit to having this special syntax built into the for loop as opposed to the time-honored
val doubleEven = for(i <- 1 to 10){
if(i % 2 == 0)
yield i*2
}
style if?
EDIT: Of cour开发者_如何学Pythonse, the latter example won't actually work. But I was curious why the Scala folks decided to go with a separate syntax.
Contents of file A.scala
:
object A {
val doubleEven1 = for (i <- 1 to 10; if i % 2 == 0) yield i * 2
}
Output of scalac -Xprint:jvm A.scala
:
[[syntax trees at end of jvm]]// Scala source: A.scala
package <empty> {
final class A extends java.lang.Object with ScalaObject {
private[this] val doubleEven1: scala.collection.immutable.IndexedSeq = _;
<stable> <accessor> def doubleEven1(): scala.collection.immutable.IndexedSeq = A.this.doubleEven1;
def this(): object A = {
A.super.this();
A.this.doubleEven1 = scala.this.Predef.intWrapper(1).to(10).withFilter({
(new A$$anonfun$1(): Function1)
}).map({
(new A$$anonfun$2(): Function1)
}, immutable.this.IndexedSeq.canBuildFrom()).$asInstanceOf[scala.collection.immutable.IndexedSeq]();
()
}
};
@SerialVersionUID(0) @serializable final <synthetic> class A$$anonfun$1 extends scala.runtime.AbstractFunction1$mcZI$sp {
final def apply(i: Int): Boolean = A$$anonfun$1.this.apply$mcZI$sp(i);
<specialized> def apply$mcZI$sp(v1: Int): Boolean = v1.%(2).==(0);
final <bridge> def apply(v1: java.lang.Object): java.lang.Object = scala.Boolean.box(A$$anonfun$1.this.apply(scala.Int.unbox(v1)));
def this(): A$$anonfun$1 = {
A$$anonfun$1.super.this();
()
}
};
@SerialVersionUID(0) @serializable final <synthetic> class A$$anonfun$2 extends scala.runtime.AbstractFunction1$mcII$sp {
final def apply(i: Int): Int = A$$anonfun$2.this.apply$mcII$sp(i);
<specialized> def apply$mcII$sp(v1: Int): Int = v1.*(2);
final <bridge> def apply(v1: java.lang.Object): java.lang.Object = scala.Int.box(A$$anonfun$2.this.apply(scala.Int.unbox(v1)));
def this(): A$$anonfun$2 = {
A$$anonfun$2.super.this();
()
}
}
}
As you can see the compiler creates a Range
from 1 to 10. Then it calls the withFilter
on that Range
to filter all even numbers. And the last step is the call of the map
-method with the multiplication with 2.
Would your second example work the semantic would be a little bit different. Because it would execute the body of the loop in every iteration without filtering.
It is a matter of taste but I would prefer this:
val doubleEven = 1 to 10 filter(_%2==0) map(_*2)
The simplest way to explain it is that the filtering occurs before the body of the for-loop is evaluated, and this syntax is designed to reflect that.
The expression for (i <- 1 to 10; if i % 2 == 0) yield i * 2
is converted by the compiler to something like:
(1 to 10).filter(_ % 2 == 0).map(_ * 2)
The filter
comes from the if
-guard, and the map
comes from the expression after the yield
keyword. It's a mistake to think of yield
as something like return
- it's actually just a keyword that tells the compiler to use the map
method instead of the foreach
method. The map
function can't ignore any of the elements of the collection that calls it, so filter
has to be called first in order to operate on only some of the elements.
You can think of the for
construct as a select/from/where query:
select (i * 2) from (1 to 10) where (i % 2 == 0)
Having the filtering predicate in the for
section rather than the yield
one also allows you to use multiple generators and filters:
for (i <- 1 to 10; if i % 2 == 0; j <- 1 to 10; if j % 2 == 1) yield { (i,j) }
You can do quite a bit without a lot of nesting and sometimes that's handy for readability if the filters aren't too complex.
精彩评论