Closed demobox closed 7 years ago
@marconilanna: What do you think? I've added some open questions to the draft as review comments...
@nermin: Ping..?
Hmmm... maybe something got lost in translation, but the spirit of the puzzle I initially discussed was that some types have lazy semantics while others are eager:
def f(x: TraversableOnce[Int]) = for {
i <- x
_ = println("ping")
} println(i)
def g(x: Range) = for {
i <- x
_ = println("pong")
} println(i)
f(1 to 2)
// ping
// 1
// ping
// 2
g(1 to 2)
// pong
// pong
// 1
// 2
Not sure which puzzler is the most interesting one, but the overall lesson here is that side effects are evil and make reasoning about code a lot harder, specially in the presence of laziness.
I initially discussed was that some types have lazy flatMap semantics while others are eager
Do you mean map
or flatMap
here? The value def becomes a call to map
, from what I recall:
def f(x: TraversableOnce[Int]) = x.map(((i) => {
val x$1 = println("ping");
scala.Tuple2(i, x$1)
})).foreach(((x$2) => x$2: @scala.unchecked match {
case scala.Tuple2((i @ _), _) => println(i)
}))
}
And yes, we have an interesting choice here between what we consider "more puzzling": the fact that value defs result in a map
that is executed before the next generator (as opposed to adding a statement to a generated, which is called in a nested fashion), or the fact that value definitions combined with lazy vs. non-lazy collections can result in unexpected behaviour.
For me, the "value def + lazy/non-lazy" option requires more complexity to "be puzzling", but I'd be curious to hear what @nermin has to say. I think it also depends on how "natural" it is to put side-effecting expressions in a value def vs. including it in a generator.
I can also live with this quite happily:
def sumsTo1Traversable(numbers: TraversableOnce[Int]) =
for {
x <- numbers
_ = println("DEBUG 1: x: " + x)
y <- x to 1
} println(x + y)
def sumsTo1Iterable(numbers: Iterable[Int]) =
for {
x <- numbers
_ = println("DEBUG 1: x: " + x)
y <- x to 1
} println(x + y)
scala> sumsTo1Traversable(1 to 2)
DEBUG 1: x: 1
2
DEBUG 1: x: 2
scala> sumsTo1Iterable(1 to 2)
DEBUG 1: x: 1
DEBUG 1: x: 2
2
There are plenty of possible variants here - using Range
, having Iterable
as the signature but calling numbers.iterator
in one of the generators etc.
The one thing to note is that the lazy behaviour of TraversableOnce.map
seems to be implementation-dependent rather than required by the documentation.
The one thing to note is that the lazy behaviour of TraversableOnce.map seems to be implementation-dependent rather than required by the documentation.
Which makes it even more "puzzlier"
@nermin: Ping..?
Discussed this with Nermin today, who likes both versions. Perhaps we can simply turn this into two puzzlers? Or combine them by having three variants of the loop?
Merged to master: ae382d3. See http://scalapuzzlers.com/#pzzlr-068
Based on a suggestion by @marconilanna