Making `for` simpler and more regular


#48

Sometimes I want to do logging inside a for expression, but the syntax for doing so is annoying. This complaint applies any method call where you want to throw away the result.

for {
...
  _ = logger.info(...)
...
}

I’d prefer,

for {
...
  logger.info(...)
...
}

#49

so be it with another one

for { 
   case if Left((x, "salmon")) <- myEither
 } yield x + x`

#50

I think you mean something like

for {
...
_ <- Option(logger.info(...))
...
}

But I agree, have had to do this also. For the Option wrapped version, I usually just create a method like def localLogger(str: String): Option[Unit] and do _ <- localLogger(...).


#51

What bothers me about for is the resulting visual clutter if you want to immediately reuse the yielded value.

(for {
  result0 <- fromService(...)
  result1 <- anotherCall(result0)
} yield result1).fold(..., ...)

The extra wrapping in a parenthesis just makes things look very messy.


#52

The exact same thing happens with if.

(if (condition) methodThatMakeCollection1() else methodThatMakesCollection2()).fold(...)

You might do this more with for, but the effect is the same, and it seems to me that any “fix” would at best not be worth the headaches and at worst would actually make the language syntax more complex while producing no significant benefits.


#53

I use the following for that:

@inline def ife[A](b: Boolean, vTrue: => A, vFalse: => A): A = if (b) vTrue else vFalse

#54

There is a canonical issue for the narrower question of what signals a filter in a generator, and I think the workaround syntax suffices:

for (x: X <- e)

aligns with

val x: X = e

but anything else is taken as a pattern. To induce filtering:

for (x @ (_: X) <- e)

An incremental improvement would be to align midstream assignment.

There was a previous PR to warn on “postfix if” following midstream assignment, because of the expectation established by perl syntax.


#55

I think it’s in most cases it’s a bad idea to immediately reuse the yielded value. Name every intermediate result! Multi-line code without local definitions is a code smell. I see too much Scala code that ignores this principle, at the price of legibility.


#56

It may be not obvious. But when I am debugging such code, this is very annoying:

  • to toggle breakpoint
  • to watch intermediate results

#57

I just mentioned this to my java friend who likes to compose a long vertically aligned expression as though it were a virtue. (He had a type inference problem.)

I wish the psychologists would explain why we are so quick to acquire certain idioms which then prove iron-clad. That is, they become mental shackles.


#58

Beautiful words. I have just remembered:
“A nuclear blaster is a good weapon, but it can point both ways”
The Foundation series Isaac Asimov


#59

I’m (kinda) resurrecting this thread here, sorry, but I found this idea appealing. If we look at ES2015’s async/await syntax for a moment, and think about it in a Scala-ish way, then we could come up with something like (to adapt https://users.scala-lang.org/t/generating-pythagorean-triples-in-scala/3833/14 ):

def pythagoreanTriples = {
  for c = Iterator.from(1) // for does the job of 'await'
  for b = 1 until c
  for a = 1 until b if c * c == a * a + b * b
  val triple = (a, b, c) // Just to show a normal assignment

  yield triple // yield does its usual job
}

True, I don’t have an analog for ‘async’, because I don’t feel that Scala needs it.

Overall, this makes it slightly more verbose (I think?) but in exchange also ‘flatter’.


#60

I personally don’t care for this – IMO, it doesn’t express scopes as clearly as the existing syntax. (OTOH, I don’t much like the async library, either, so take this opinion for what it’s worth.)


#61

I feel that scoping is orthogonal to comprehensions. Mixing them together results in special cases. We see now in for-comprehensions that certain syntactic constructs are illegal, e.g. you can’t do:

for {
  import Foo._

  a <- b
  c <- d
} yield f(a, c)

#62

In most cases, yes. But I still occasionally run into the case of wanting to stick a (collections.breakout) at the end of a comprehension so that the types get massaged into the correct shape.

Some combination of SIP-12 (as in dotty), changing the parse rules to use those of better-monadic-for (is this in dotty yet?), and the 2.13 collections rewrite should make this problem go away


#63

“It’s just syntax” cuts both ways.

scala 2.13.0-M5> for (i <- Option(42) ; i <- Option(3)) yield i
res0: Option[Int] = Some(3)

Probably we lend too much weight to that slogan and also the word “orthogonal”.


#64

No, and this won’t happen until someone writes a SIP for it (and other changes to for-comprehension that can achieve consensus), we’re waiting for the community to do that.


#65

Sorry, I don’t understand your point :slight_smile:


#66

I meant that for translation obscures scoping and is not an orthogonal concern.

Rereading, I see your point was that it should be orthogonal, and that a brace should just introduce a block like anywhere else.

Probably my observation makes more sense as a response to the previous implication that existing syntax expresses scopes clearly.

The topic summary says it has “an estimated read time of 16 minutes .” But that doesn’t include re-reading for context.


#67

Well, say goodbye to that with Scala 2.13 :wink: I already went through my code base and removed in several dozen places … :expressionless: