Surprising line continuations in Scala 3

I was surprised to see that this doesn’t compile:

val && : Int = 5
&& + 1

It doesn’t compile because it parses the same as val && : Int = 5 && + 1. The spec is not terribly clear on whether this is intentional – it says that

… if the operator appears on its own line, the next line must have at least the same indentation width as the operator.

but it does not explicitly state what happens when the operator is not on its own line.

I find it very surprising that, in a language with significant whitespace, it is not required that multi-line expressions be indented. All examples in the linked doc are indented and I think that this the de facto standard.

Is it too late to change the parser behavior here?

3 Likes

The spec you cite was a late-breaking tweak, or late-tweaking, to accommodate:

def f() =
  println("debug")
  ???
println("debug")

where if the lines are joined, ??? becomes postfix.

Vertical alignment was also a later tweak, IIRC from backporting a couple of such tweaks to -Xsource:3, which I think now is still only mostly correct.

So, the answer to the question is that it’s never too late to muck with the spec.

It seems to me there was a conversation about “leading infix” as syntax not long ago, because I recall that the operator is free to start the next line, without any indentation. I’m sorry I can’t remember the context or why that seemed like a great feature.

Actually, you can’t outdent to top level (where there are no braces):

class C:
  def f() =
    println("debug")
    ???
  println("debug")

  def g = 1
  + 42
 + 17
+ 27 // end of toplevel definition expected but identifier found

def k = 1
+ 2

It certainly seems to me that

1
+2

Should not be the same as 1+2. There was this discussion, where Martin explicitly wanted the unary + to be a leading infix operator. Very surprising to me.

4 Likes

Thanks for the link, that was the discussion I was looking for.

I’d forgotten this one:

Note that “Odersky interpolation” syntax doesn’t work as leading infix:

def y = "a "+ b +" c"
def y =
  "a "
+  b
+" c"

The aborted tweak for unary is at this PR. The discussion was Functional Syntax?

I commented on the PR that I liked the space rule and would have volunteered a weekend to backport it. Come to think of it, “Odersky interpolation” syntax also relies on a space rule of a kind, where +"c" is neither positive nor detached but infix. I’m not sure how to formulate that rule.

Looking back, I see the use case for operator in column 1 was:

Under such a rule, the + could be in column 1, which looks like a weird DSL for diff.

  def y =
    "a"
+   "b"
-   "c"

Should I take that as a vote of support for clarifying the spec here, and treating indentation as significant for multi-line infix? Should I open an issue?

Indentation is not significant for multi-line infix. If it was, you could not write the following:

    x
  + y
  + z

But that’s a valuable capability to have.

I see I didn’t fully understand that that was the motivating use case. I can see why you might want to align complex boolean expressions that are all conjoined with &&, though for me

(complex && (boolean || expression))
  && (other || complex (boolean && expression))
  && (last && line)

is just as readable as

     (complex && (boolean || expression))
  && (other || complex (boolean && expression))
  && (last && line)

I tried using the aligned syntax in Scastie, and format (which I assume uses scalamt) formatted it away. Perhaps one can take those as indirect votes on the the frequency of the aligned syntax? I know that scalafmt is not an authoritative source on the language, but I think scalafmt is common enough that it’s a de facto standard?

I’m still stuck writing boolean exprs in Scala 2 without -Xsource:3 and it’s infuriating, or it would be infuriating if I hadn’t learned to focus on the breath, light a candle, relax the shoulders, and usually after that I get another cup of coffee.

Styles for booleans vary widely, and I remember early advice to “always completely parenthesize”. Personally, I find the gutter of operators on the left visually compelling.

I think scalafmt must cater to a broad taste.

Edit: sample missed opportunity for alignment

      def isAlwaysSafe(meth: Symbol) =
        (meth eq defn.Object_eq)
        || (meth eq defn.Object_ne)
        || (meth eq defn.Any_isInstanceOf)

Don’t get me wrong, I do like the aligned syntax! I just think in my original example, most programmers (expert and novice) would be very surprised, whereas few would be surprised that you have to indent a multi-line expression, particularly in a language with significant indentation.

1 Like

How about this syntax-oriented spec tweak

def f =
  val && : Int = 5
  `&&` + 1
def g =
  27
  `to`
  42

where backticks make non-operators eligible for leading infix, with the tweak that backticks also make operators ineligible for leading infix.

This week’s free archived crossword from Thursday, December 5, 2013 has the theme clue “puzzlement” for “confusion”, where ten answers on the perimeter require the fusion of the prefix con, such as con + current (which is incorrectly (!) clued as “simultaneous”). It recalls the coarse keynote on “co-things”.

It’s mere coincidence that December 2013 marked the birth of the dotty project.

My point is there will be confusion!

To my personal preference, using { && + 1} is clearer in than using backticks.

As another data point, one of the reasons that postfix operators were put behind a flag in scala 2 was that they “interact poorly with semicolon inference”. I.e. if you don’t insert an empty line they don’t always work as you would expect. Which seems to be the case here too.

1 Like