Syntax Proposal - Pattern matched functions


#1

I believe this is the correct place to make such a proposal; if I’m wrong, please point me to the proper location.

Problem

It’s common for people to define functions that match entirely on their arguments, especially when working with ADTs:

def concat[A](xs: List[A], ys: List[A]): List[A] = (xs, ys) match {
  case (Nil, Nil) => Nil
  case (Nil, ys) => ys
  case (xs, Nil) => xs
  case (xs, ys) => xs ++ ys
}

It’s somewhat cumbersome to have to create a tuple just to match on the arguments of the function.

Proposal

Permit the following:

def concat[A](xs: List[A], ys: List[A]): List[A] = {
  case (Nil, Nil) => Nil
  case (Nil, ys) => ys
  case (xs, Nil) => xs
  case (xs, ys) => xs ++ ys
}

Which is interpreted as the above example.

This also applies to val function definitions:

val sum = (x: Int, y: Int) => {
  case (0, 0) => 0
  case (0, y) => y
  case (x, 0) => x
  case (x, y) => x + y
}

It should be noted that the following syntax already works (in Scala 2.x):

val sum: (Int, Int) => Int = {
  case (0, 0) => 0
  case (0, y) => y
  case (x, 0) => x
  case (x, y) => x + y
}

Partial Functions

PartialFunction currently uses this syntax, albeit, it requires an explicit type-declaration:

def concatEmpty[A]: PartialFunction[(List[A], List[A]), List[A]] = {
  case (Nil, Nil) => Nil
}

Note: there are no parameters on the above, as a PartialFunction takes its parameters when it’s applied.

When a function producing a PartialFunction takes parameters, those parameters become part of the PF definition, and not its evaluation:

def foo[A](x: Int): PartialFunction[(List[A], List[A]), Int] = {
  case (Nil, Nil) => x
}

This means we can prevent a conflict by adding the rule that this syntax re-writing is never done when the result type of a function is a PartialFunction.


#2

This would also create an ambiguity for the following code:

def foo(x: Int, y: Int): (Int, Int) => Int = {
  case (a, b) => ???
}

You wouldn’t know whether it is supposed to mean

def foo(x: Int, y: Int): (Int, Int) => Int = { (f, g) =>
  (f, g) match {
    case (a, b) => ???
  }
}

(as it currently does) or

def foo(x: Int, y: Int): (Int, Int) => Int = {
  (x, y) match {
    case (a, b) => ???
  }
}

as your new desugaring would ask.

This means we can prevent a conflict by adding the rule that this syntax re-writing is never done when the result type of a function is a PartialFunction.

Adding a rule to compensate a conflict created by the addition of new desugaring is a language design smell.


#3

This would also create an ambiguity for the following code:

def foo(x: Int, y: Int): (Int, Int) => Int = {
case (a, b) => ???
}

I hadn’t considered that. Looks like this is a non-starter. Thanks for finding the hole in my reasoning, I thought I had it all figured out!