Pre SIP: Demote match keyword to a method

In Scala 3, would it be possible to make match a method that get injected to any type A?

pipe vs match

scala> final class ChainingOps[A](private val self: A) extends AnyVal {
     |   def pipe[B](f: A => B): B = f(self)
     | }
defined class ChainingOps

scala> implicit final def chainingOps[A](a: A): ChainingOps[A] = new ChainingOps(a)
chainingOps: [A](a: A)ChainingOps[A]

scala> "foo".pipe({ case "foo" => 1; case _ => 2 })
res2: Int = 1

scala> "foo" match { case "foo" => 1; case _ => 2 }
res3: Int = 1

In the above, I am assuming that the semantics of the pipe call and the pattern match expression is the same. Internally there’s a magical optimization that turns pattern match into a series of if-else etc, but the meaning is the same as invoking the function.

motivation

One less thing for the language.

When chaining, we can use dot-and-paren syntax:

scala> :paste
"foo"
  .match({ case "foo" => 2; case _ => 3 })
  .match({ case x if x % 2 == 0 => -7 * x; case _ => 4 })
  .match(scala.math.abs)

// Exiting paste mode, now interpreting.

res12: Int = 14

Supports total functions.

9 Likes

FYI, a long time ago, match was a method of scala.Any.

removed 2006 from spec.

2 Likes

I proposed this a while ago, although I didn’t really pursue it.

Do you have a link to the proposal? Were there any feedback to it?

I didn’t mean “proposed” in that sense. I suggested it on Gitter. I was told to open an issue on the dotty github repo, but I don’t think I ever did.

From a user perspective I have always wondered why it’s implemented as a keyword instead of a method. So its definitely worth considering to remove this potential source of confusion.

1 Like

Is there a way that allows for tail recursion elimination in the match method, without special-cases the match method in the compiler (at which point it could just as well be a language construct again)?

4 Likes

I think of it as similar to the concat + method for Strings. The compiler inlines + calls to use StringBuilder etc, but on the surface the syntax looks like a normal infix method call.

match also could look like a method call, but still retain the current inlining behavior.

You could do that, but then, what’s the upside? You claim its one less thing for the language,
but in reality, it’s still part of the language. I can’t consider something that looks like a regular method, but has and needs support in the compiler to support the desired semantics as not part of the language.

And in addition, while it is made to look like a normal method, it has special non obvious features that normal methods do not have, and will do tail call elimination (but only if the function passed to match method is a literal).

I would really like it if scala were powerful enough that match could be a library-level construct rather than a compiler-level construct. But it’s not.

3 Likes

Any methods attributed to Any or AnyRef that are not already part of java.lang.Object have to be compiler special cases, because we cannot add new methods to java.lang.Object.

I’m assuming the purpose of the proposal is not to make the compiler simpler, but just to reduce the number of keywords.

I agree that the .match syntax is nice. But there are some recent developments which require that match is syntax.

Here’s a direct link to the typelevel.md file in this PR. The relevant bit is that it introduces two new forms of match:

rewrite expr match { cases }
implicit match { cases }
1 Like

It seems the only choice would be whether to allow calling match with dot syntax and to chain matches — even without the new proposals, pattern matching is a core language construct anyway.

1 Like

This would be interesting, if it was possible to do everything special about a match in user-space.

Looking at it through that lens, what features does the language need to have in general available to user-space to make that possible? Could the proposed additions of rewrite or inline work outside of a match context somehow?

These are all good questions to ask IMO, and if the day ever comes where one could write their own match method that did everything the language one could, then it would make sense to demote it to a method.

3 Likes

Match right now has two features which are incredibly important:

  1. You can return from it directly, efficiently. (Very useful for error handling.)
  2. It is zero-overhead compared to the equivalent if-statements.

If we can make ordinary methods have both of these properties, great! If not, I think discarding match as a fundamental language capability is counterproductive.

4 Likes

I don’t think it should be implemented as a method, perhaps it should be grammatically though.

Barring that, or in any case, we should try to make ordinary methods such as pipe / |> have those abilities. Then we can just always use |> instead of match. :wink:

1 Like

Yes. I should clarify that I want all the inlining, optimizing behavior of match as language construct, but with the syntactic shape of normal method call.

Why match can have less overhead than if?

It doesn’t. It doesn’t have no overhead over the equivalent if statements, not, er, underhead.

Pattern matching is core, but it’s already accessible with the partial function syntax (i.e., curly-brackets-and-cases). Applying pattern matching on a value does not need special syntax per se.

Now, I know the compiler internally encoded everything using match, but I think that’s orthogonal to considerations about the surface language.

2 Likes

I think it is possible by making match be an inline method.

https://docs.scala-lang.org/sips/inline-meta.html
https://dotty.epfl.ch/docs/reference/inline.html