Proposal: Optional curly braces after match for single case

Often time I have situation to use pattern matching for destructuring an object like:

getTransaction(100, "Amount") match {
  case Transaction(num, name) => println(s"$name has amount of $num")
}

It is a single case match used like piping.

If I omit the curly braces, it emits error error: '{' expected but 'case' found.

It will be convenient to allow omission of curly braces like:

getTransaction(100, "Amount") match case Transaction(num, name) => println(s"$name has amount of $num")

I understand dotty will have optional curly braces, but I believe this case is not covered. It could also be backported in Scala 2 syntax.

Dotty will have automatic untupling, so you will be able to get pretty close:

import scala.util.chaining._
import scala.language.implicitConversions

@main def main(): Unit = 
  (100, "test") pipe { (num, name) => println(s"$name has amount of $num") }

Scastie

The downsides are it requires two imports, one of which enables a feature considered dangerous, and the braces around the method are required (otherwise the compiler thinks that (num, name) are arguments to pipe).

Incidentally, I’d love the automatic untupling to be backported, but I’m not sure how much work that would be for the compiler team.

On a complete tangent, I have no idea why chaining isn’t easier to bring in (I’d love it in Predef, to be honest), but hopefully the implementation will be changed to something that doesn’t involve implicit conversions and remove that wart.

My example is not comprehensive enough. It could be a tuple to be untupled, or it could be any case class to be destrcutured.

We could also do the same thing to try catch, like:

try {
  throw new Exception()
} catch case _: Exception => {
  println("Exception caught")
}

instead of

try {
  throw new Exception()
} catch {
  case _: Exception => {
    println("Exception caught")
  }
}

I agree pipe should be easier to use, and I think with this change, it is very close to what I try to achieve. I definitely prefer a simple match case to pipe { case }. It avoids additional imports and additional lambda construct

I would like to see

getTransaction(100, "Amount") match Transaction(num, name) => {
  println(s"$name has amount of $num")
}
3 Likes

It already works for try:

try "hello" catch case ex: Exception => "?"

The reason it does not work for match is that the two play different roles in the syntax.
matches can be chained and mixed with normal calls. That means

expr match {
  ...
}

is seen as an expression with fairly high precedence (same as alphanumeric operators). But that convention can be upheld only if the right hand side of match is atomic.

Anyway, I think try is the more important usecase since a single case match can also expressed as a pattern binding:

val Transaction(num, name) = getTransaction(100, "Amount")
println(s"$name has amount of $num")
4 Likes

In it’s current form, the match expression can be understood as:

[value] match [partial function literal]

And partial function literals are delimited by curly braces.

If you have a single-clause match, you can also write it as an assignment:


val (num, name) = (100, "Amount")

3 Likes

Great to see it already works with try-catch! Is there any plan to backport that into Scala 2?

Yes val can also destructure the object, but it requires two statements as you mentioned. When we have a series of objects to process, it gets tedious. I definitely agree that the convention can be upheld if it is a single case on the right hand side.

that makes sense!

1 Like

I wonder if it could be possible to syntactically allow (partial) function variables after a match, especially since matches can now be chained.

Dummy example:

def f(i: Int) = i * 2
val g: PartialFunction[Int, String] = { case 2 => "two" }

And then have:

1 match f match g // or 1.match(f).match(g)

Once you named your partial function, isn’t it simpler to just apply it, instead of matching?

1 Like

True. But if you have a few matches in a chain, some of them being in { case …} form, some of them being pre-defined functions, you will have to flip the sides around every time; having the thing to match with once on the left side, other times on the right side (and additionally have need to invent new variable names for the in-between steps) which seems a little confusing.

The alternative would be to write match { case x => f(x) }. This is a total match, so it makes sense to maybe allow omitting the case, leaving match { x => f(x) }, which again feels like it could be shortened as usual to match f.

I guess part of my reasoning comes from the fact that I already can define (or import) my own pipe operator @alpha("match") def [A, B](x: A) |> (y: Function[A, B]): B = y(x) which gives me exactly that (or so it seems), so it is not totally clear to me why match has to be more restrictive, unless there are syntactic or type-checking reasons in the way.

@odersky How can I move forward with this proposal? I can also help with the implementation. Shall we implement this in dotty with all the other syntax changes?

I already explained why it’s problematic to move forward with the proposal. So it’s not a matter of implementation. The implementation existed but was not merged for the problems I mentioned. I do not see what could make the problems go away.

1 Like

Can you elaborate a little more on why omitting the curly braces will make the expression with single case less high precedence?