scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.88k stars 1.06k forks source link

fewerBraces: allow partial function arguments without `:` #17731

Closed sjrd closed 1 year ago

sjrd commented 2 years ago

It is quite common to pass anonymous partial function to higher-order functions (whether they take a PartialFunction or a regular Function1). In that case, there is no explicit parameter to the lambda, which requires the use of : for the fewerBraces mechanism to kick in:

object Test {
  def main(args: Array[String]): Unit =
    val xs = List((1, "hello"), (true, "bar"), (false, "foo"))
    val ys = xs.collect:
      case (b: Boolean, s) if b => s
    println(ys)
}

I suggest that we infer the : in that situation, so that we could write instead:

object Test {
  def main(args: Array[String]): Unit =
    val xs = List((1, "hello"), (true, "bar"), (false, "foo"))
    val ys = xs.collect // note: no ':' here
      case (b: Boolean, s) if b => s
    println(ys)
}

Currently, the above fails to parse:

-- [E040] Syntax Error: tests/run/hello.scala:5:6 ------------------------------
5 |      case (b: Boolean, s) if b => s
  |      ^^^^
  |      unindent expected, but 'case' found
-- [E040] Syntax Error: tests/run/hello.scala:7:0 ------------------------------
7 |}
  |^
  |'}' expected, but unindent found
-- [E006] Not Found Error: tests/run/hello.scala:6:12 --------------------------
6 |    println(ys)
  |            ^^
  |            Not found: ys
  |
  | longer explanation available when compiling with `-explain`
3 errors found
odersky commented 2 years ago

I's a difficult tradeoff between conciseness and consistency. I would leave it out of the first version of fewerBraces. We can always add it later.

alexandru commented 1 year ago

If you care for a random personal anecdote … I'm teaching my son and my nephew Python, both teens, and the syntax has been a big roadblock. I also don't have teaching experience, so it caught me off guard, especially since I thought Python was easy to understand for beginners. A point of confusion, besides the perennial misunderstanding of the evaluation model, is the syntax for code blocks. They have been confused about where blocks of code should be.

And having a consistent rule like “a block of code must follow a : char, always” helps a lot because they can rote learn such rules. Skipping over the fact that teens have no appreciation for whitespace or its proper use, this is where Python is more consistent than curly braced languages, since an if, while, for, and other statements always end with a : char. Whereas in C-family languages, the curly braces are optional if the block of code is made of a single expression.

I think that consistency helps when learning languages, I think that's because it helps us to recognize patterns that we can later reuse in other instances, which is why exceptions to rules are bad for the learning process. And the ideal would be to see how students struggle to learn a syntax before adopting it. YMMV.

odersky commented 1 year ago

@alexandru It's not that this has not been studied. See for instance http://okasaki.blogspot.com/2008/02/in-praise-of-mandatory-indentation-for.html. But yes, YMMV, I don't think it's possible to get consensus on this.

alexandru commented 1 year ago

@alexandru It's not that this has not been studied. See for instance http://okasaki.blogspot.com/2008/02/in-praise-of-mandatory-indentation-for.html. But yes, YMMV, I don't think it's possible to get consensus on this.

Thanks for the link by Chris Okasaki.

Here I was thinking about the issue at hand: having cases in which : can be avoided in place of a {} block, and having it as optional. I was saying that I appreciate Python's consistency in this regard. Avoiding the overloading of syntax or the complexity of remembering when indentation is significant, were AFAIK among the reasons for why multi-line anonymous functions did not make it in Python (a relevant link).

Similar cases already exist. For example, in a for {} yield {}, it's just for\n, and not for:\n. A case can be made that for is special and that the curly braces don't introduce a lexical block of code (as the for introduces a lexical scope on each <-). I also noticed that adding : after for doesn't work, so it's not optional. Which is good.

Based on this example, one could also make the case that the syntax for anonymous partial function is similar. Each of the cases introduces a scope, and this is different from a block of code passed as a parameter to a normal function. However, one can also make the case that partial functions are confusing as a concept, and that missing the : may lead to more confusion, and has to be carefully considered. Especially because we can't get rid of the :, due to how code can change:

// (1)
list.map: x =>
  x match:
    case Odd => 1
    case Even => 0

// (2) can be refactored to
list.map:
  case Odd => 1
  case Even => 0

// (3) versus
list.map
  case Odd => 1
  case Even => 0

Requiring sample no. 3 and banning sample 2 would be confusing. So, this issue talks about having : as optional. I don't think having : as an optional char helps, with teaching or otherwise. Just an opinion from limited experience.

odersky commented 1 year ago

Yes, that's a good point. I also think we should avoid making : optional.