for (c <- "Hello"; i <- 0 to 1) yield (c + i).toChar
why the above line yield is different from the below line yield
for (i <- 0 to 1; c <- "Hello") yield (c + i).toChar
Even though (c + i).toChar is same for the above lines and the output should be the same but it is different.
The demonstrates that the order of generators is significant. (Every <- is a "generator".)
The 1st generator is "slower" in that it advances only after the 2nd generator completes a cycle.
The 1st generator also guides the output collection type. If it iterates through a String then the output is a String, if the output elements are still Chars. If it iterates through a Range then the output is an IndexedSeq[]. (According to the Scala docs, Range is "a special case of an indexed sequence.")
Related
I'm new to Scala, and I'm working through Scala for the Impatient by Cay Horstmann. It was going well until I got to for comprehension, and came up against this slightly cryptic passage (section 2.6, Advanced for Loops and for Comprehensions):
[Start quote]
The generated collection is compatible with the first generator.
for (c <- "Hello"; i <- 0 to 1) yield (c + i).toChar
// Yields "HIeflmlmop"
for (i <- 0 to 1; c <- "Hello") yield (c + i).toChar
// Yields Vector('H', 'e', 'l', 'l', 'o', 'I', 'f', 'm', 'm', 'p')
[End quote]
Indeed, running this in the REPL, I see that the result of the first code snippet has type String, and the second code snippet has type scala.collection.immutable.IndexedSeq[Char].
Why do the types differ? I think I understand the second line of code, but I don't understand why the first line doesn't also have type scala.collection.immutable.IndexedSeq[Char]. What magic is happening to get a String rather than a Vector? What does the author mean by "compatible with the first generator"?
Both flatMap and map are trying to build an object of the same type if possible. The first example is effectively:
"Hello".flatMap { c => (0 to 1).map { i => (c + i).toChar } }
since you are calling String#flatMap (StringOps#flatMap to be exact), it will try to build String, and it's possible because internal collection returns a collection of Chars (try to remove toChar and you will see something very different).
In the second example:
(0 to 1).flatMap { i => "Hello".map { c => yield (c + i).toChar }}
it's impossible to generate a valid Range, so Range#flatMap falls back to Vector.
Another interesting example:
Map(1 -> 2, 3 -> 4).map(_._1) //> List(1, 3)
Normally Map#map will try to generate Map, but since we don't have pairs it's impossible, so it falls back to List
UPDATE
You can even use this trick if you want to generate something different from the default (e.g. I want to generate a list of Chars instead):
for {
_ <- List(None) // List type doesn't matter
c <- "Hello"
i <- 0 to 1
} yield (c + i).toChar //> List(H, I, e, f, l, m, l, m, o, p)
To see why:
$ scalam -Xprint:typer
Welcome to Scala 2.12.0-M5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_92).
Type in expressions for evaluation. Or try :help.
scala> for (c <- "Hello"; i <- 0 to 1) yield (c + i).toChar
[[syntax trees at end of typer]] // <console>
val res0: String = scala.Predef.augmentString("Hello").flatMap[Char, String](((c: Char) => scala.Predef.intWrapper(0).to(1).map[Char, scala.collection.immutable.IndexedSeq[Char]](((i: Int) => c.+(i).toChar))(immutable.this.IndexedSeq.canBuildFrom[Char])))(scala.Predef.StringCanBuildFrom);
The StringCanBuildFrom builds strings, of course.
There are many duplicates of this question, because everyone is mystified.
The collection you are using for the first generator is the class that is used for the translation from for expression to operations as described in the Scala Documentation:
Scala’s “for comprehensions” are syntactic sugar for composition of multiple operations with foreach, map, flatMap, filter or withFilter. Scala actually translates a for-expression into calls to those methods, so any class providing them, or a subset of them, can be used with for comprehensions.
I'm new to Scala so please bear with me.
I'm confused about the behaviors below:
val l = List(Option(1))
for (i <- l; x <- i) yield x //Example 1: gives me List(1)
for(x <- Option(1)) yield x //Example 2: gives me Some(1)
Why doesn't the second for comprehension give me 1 instead? Because that would look more consistent to me, intuitively, since the second for comprehension in the first example x <- i looks like it should behave exactly the same way as the second example, as the second example basically has extracted the option out of the list to begin with.
Simply put, for comprehension wraps into the type that was used the first time.
for (x <- Option(1)) yield x // Returns Option
for (x <- List(1)) yield x // Returns List
for (x <- Array(1)) yield x // Returns Array
This:
for (i <- List(Some(1)); x <- i) yield x
Desugares into this:
List(Some(1)).flatMap { case i => i.map { case x => x } }
flatMap of List returns List[T], that's why it behaves like that
I would to print out a stream of numbers but the following code only prints out the first number in the sequence:
for ( n <- Stream.from(2) if n % 2 == 0 ) yield println(n)
2
res4: scala.collection.immutable.Stream[Unit] = Stream((), ?)
In Haskell the following keeps printing out numbers until interrupted and I would like similar behaviour in Scala:
naturals = [1..]
[n | n <- naturals, even n]
[2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,
Instead of yielding just println (why would one want infinite sequence of Unit's?):
for ( n <- Stream.from(2) if n % 2 == 0 ) println(n)
If you really want that infinite sequence of Units, force result:
val infUnit = for ( n <- Stream.from(2) if n % 2 == 0 ) yield println(n)
infUnit.force // or convert to any other non-lazy collection
Though, eventually, it will crash program (due to large length of materialized sequence).
The result type of a for comprehension is a collection of the same type of the collection in the first clause. See the flatMap function signature
So the result of a
for ( n <- Stream.from(2) .....
is a collection of type Stream[_] which is lazy so you have to pull the element values or actions out.
Look at the result types:
scala> :type for( n <- Stream.from(2)) yield n
scala.collection.immutable.Stream[Int]
scala> :type for( n <- List(1,2,3)) yield n
List[Int]
scala> :type for( n <- Set(1,2,3)) yield n
scala.collection.immutable.Set[Int]
To print out numbers until interrupted try this:
Stream.from(2).filter(_ % 2 == 0) foreach println
Its type grants us it will work:
scala> :type Stream.from(2).filter(_ % 2 == 0) foreach println
Unit
I think you meant:
for (n <- Stream.from(2) if n % 2 == 0) yield n
(because yield println(n) will always yield () with a side effect of printing n)
This gives you the collection you want. However, Scala, unlike Haskell, doesn't evaluate all members of a (lazy) list when you print the lazy list (a Stream). But you can convert it into a non-lazy list using .toList. However, you won't see the same infinite printing behaviour in Scala as it will try to build the entire (infinite) list first before printing anything at all.
Basically there is no way to get the exact same combination of semantics and behaviour in Scala compared to Haskell when printing infinite lists using the built-in toString infrastructure.
P.S.
for (n <- Stream.from(2) if n % 2 == 0) yield n
is expressed more shortly as
Stream.from(2).filter(_ % 2 == 0)
I'm reading through Scala for the Impatient and I've come across something that's got me scratching my head.
The following returns a String:
scala> for ( c<-"Hello"; i <- 0 to 1) yield (c+i).toChar
res68: String = HIeflmlmop
But this returns a Vector:
scala> for (i <- 0 to 1; c <- "Hello") yield (c + i).toChar
res72: scala.collection.immutable.IndexedSeq[Char] = Vector(H, e, l, l, o, I, f, m, m, p)
The text preceding these two examples reads...
"When the body of the for loop starts with yield, then the loop
constructs a collection of values, one for each iteration...This type of loop is called a for comprehension. The generated collection is compatible with the first generator.
If the generated collection is compatible with the first generator, then why isn't the second example returning a type of Range, as in the following:
scala> val range = 0 to 1
range: scala.collection.immutable.Range.Inclusive = Range(0, 1)
Or am I misinterpreting entirely what the text means by, "...the generated collection is compatible with the first generator."
for-comprehensions are desugared to a series of map, flatMap and filter operations.
When you use map on a Range, you get a Vector output:
scala> 0 to 2 map (x => x * x)
res12: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 1, 4)
This is because a Range is a very simple sort of collection, that is essentially just two three numbers: a start value, an end value and a step. If you look at the result of the mapping above, you can see that the resulting values cannot be represented by something of the Range type.
in this for (i <- 0 to 1; c <- "Hello") yield (c + i).toChar comprehension,
the 1st generator is of Type scala.collection.immutable.Range.Inclusive
the yield result vector is of Type scala.collection.immutable.IndexedSeq[Int]
and if you check the class Range:
http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.Range
it shows Range extends/mixin the IndexedSeq. the super type IndexedSeq is compatible with the sub type Range.
If the result can not be represented by a range(as the previous answer explained), it will 'search for' the super type to represent the result.
How is below loop being incremented ?
for(i <- 1 to 3; j <- 1 to 3) print((10 * i + j) + " ")
Is there an implicit counter using 'to' ?
for is actually shorthand for applying a bunch of collections methods. In particular, if you are not using yield, each statement in the for selector is translated to foreach. So
for (i <- 1 to 3; j <- 1 to 4) f(i,j)
turns into
(1 to 3).foreach{ i => (1 to 4).foreach{ j => f(i,j) } }
foreach is a method on all collections--Range included, which is what 1 to 3 turns into--which loops through each item in the collection, calling a provided function each time. A Range's items are the numbers listed (endpoints included, in this case)--in fact, Range doesn't actually store the numbers in a separate list, so it's main purpose is precisely to hold ranges of numbers for exactly this sort of iteration.
In scala, the for construct is like the "foreach" construct in Java. The following sets i to be each successive item in the given Iterable.
scala> for(i <- Seq(1, 2, 3)) println(i)
1
2
3
The to operator, as in 1 to 3 constructs a Range from 1 to 3:
scala> 1 to 3
res3: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3)
There is an implicit conversion from Int to RichInt.
RichInt defines the function to() which returns a Range.
Range is a collection, and has foreach() hence it can be used in a for comprehension (which is just syntactic sugar for foreach()).