If this was true, it would lead to some counter-intuitive behaviour in code like this:
def f(x: Option[List[Int]]): Option[Int] = for {
lst <- x
} yield lst.sum
I want to interpret it as ... yield (lst.sum) but what you suggest is to interpret it as (... yield lst).sum. I think, your suggestion is generally bad.
The result you’re getting is actually what I would expect. This looks like the foreach is part of the yield to me, and that’s what is happening.
If I’m understanding you correctly, you want the foreach to be based on the result of the entire for comprehension. I would expect that to be written something like:
(for {
s <- Option("some")
} yield "thing").foreach(println)
Enclosing for comprehesion in parentheses is what I do from time to time and I also agree on the lst.sum example. However, everytime I have to enclose a for comprehension with a yielded value that is enclosed in curly braces, I think that this should not be necessary.
I think that enclosing the yielded value in curly braces could make a difference, but I also understand that this might baffle others.
Well, then you are somewhat understandable since we have another precedence in case of usual method, not a yield keyword:
def f(x: Option[List[Int]]): Int =
{
// whatever computation
x
} map { lst =>
// whatever computation
lst.sum
}.getOrElse(0)
Here expression after the dot (i.e. getOrElse in this case) after the second curly braces applies not to the expression inside the curly braces but to the outer expression. If yield was instead of map (assuming appropriate beginning of the method), precedence is reverse. Maybe, this is what messes you up.
Of course, that behavior is easily explained by the fact that Scala accepts methods with a single argument in infix notation. To Scala, your code here is seen as the following after being desugared.
def f(x: Option[List[Int]]): Int =
{
// whatever computation
x
}.map{ lst =>
// whatever computation
lst.sum
}.getOrElse(0)
Now that precedence makes total sense. I don’t see the argument for yield to behave similarly since it isn’t a method. Unlike match, you can only “apply” a yield to a for loop.
Hmm well, method or no method, it would indeed be more regular to interpret for {...} yield {...}.foo as(for {...} yield {...}).foo and for {...} yield x.foo as for {...} yield {x.foo}. I must admit it’s irritated me before.
Edit:
Well actually, a bit to my surprise, yield does work more or less as could be expected: https://scastie.scala-lang.org/BKKtBnmORzeLgqCc9L9Qnw
It’s because yield is always infix that it behaves that way. So if you would be able to call it with a dot like a regular method that would indeed help. But it would look weird at the same time.
Normal for what? Pretty much every modern language that has a for loop allows it to declare one or more variables that appear in the body of the loop. I would say that is normal behavior in the case of a for loop. It is what people expect. Yes, it is irregular in terms of standard method call syntax, but things that are language keywords often are.
Personally, I wish that yield wasn’t required and the compiler could tell if the loop was being used as an expression or a statement and generate the appropriate code based on that. As it is, including yield when you don’t use the result won’t make code incorrect, it just potentially wastes resources.
Most “normal” languages have if/else as a statement, not an expression. But it works better with a FP approach to have it an expression, and also is more regular (in that everything is an expression, even if the expression returns Unit because there’s nothing better it could do).
scala> val x: Int = if (true) 3 else 4.toString
<console>:11: error: type mismatch;
found : String
required: Int
Like for yield, if you want the method to apply to the whole thing, you have to assign it to a value or wrap it in parens. Changing for yield to not require that would be comfortable for some situations, but inconsistent with other parts of the language. I suspect this is one of those cases where no matter which way the language goes, people will complain either way.
I would note that anything descended from C acknowledges the usefulness a conditional expression with the ternary operator. All Scala did was make the simplification that a normal if is an expression so they don’t need a ternary operator.