views:

174

answers:

3

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 course, the latter example won't actually work. But I was curious why the Scala folks decided to go with a separate syntax.

+7  A: 

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)
michael.kebe
Thanks for the `-Xprint:jvm`, that's very useful.
Jesper
Some others: `-Xprint:all`, `-Xshow-phases` And try `-X` to see more options and `-Y`.
michael.kebe
+6  A: 

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.

huynhjl
+7  A: 

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.

MJP
Thanks a lot; that makes sense.
aharon