15

In Scala, you can do

list.filter { item =>
    item match {
      case Some(foo) => foo.bar > 0
    }
}

But you can also do the quicker way by omitting match:

list.filter {
  case Some(foo) => foo.bar > 0
}

How is this supported in Scala? Is this new in 2.9? I have been looking for it, and I can figure out what makes this possible. Is it just part of the Scala compiler?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Amir Raminfar
  • 33,777
  • 7
  • 93
  • 123
  • In fact, your first variant is invalid. item is not defined (and should it be, it would not mean an element of the list). filter expect a function, you must write list.filter{item => item match... It is not just match that disappear. – Didier Dupont Aug 23 '11 at 22:09
  • 1
    @didierd, I edited the question to address your valid point, because I think there's still an interesting question here. – Kipton Barros Aug 23 '11 at 22:14
  • I didn't see anyone answering this point, but this has existed since I learned Scala, at the very least -- back on 2.7.x. – Daniel C. Sobral Aug 24 '11 at 15:08

3 Answers3

18

Edit: parts of this answer are wrong; please refer to huynhjl's answer.


If you omit the match, you signal the compiler that you are defining a partial function. A partial function is a function that is not defined for every input value. For instance, your filter function is only defined for values of type Some[A] (for your custom type A).

PartialFunctions throw a MatchError when you try to apply them where they are not defined. Therefore, you should make sure, when you pass a PartialFunction where a regular Function is defined, that your partial function will never be called with an unhanded argument. Such a mechanism is very useful e.g. for unpacking tuples in a collection:

val tupleSeq: Seq[(Int, Int)] = // ...
val sums = tupleSeq.map { case (i1, i2) => i1 + i2 }

APIs which ask for a partial function, like the collect filter-like operation on collections, usually call isDefinedAt before applying the partial function. There, it is safe (and often wanted) to have a partial function that is not defined for every input value.

So you see that although the syntax is close to that of a match, it is actually quite a different thing we're dealing with.

Community
  • 1
  • 1
Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234
  • What you say makes sense, but according to tests that I posted in my answer, the two expressions seem to behave exactly the same. Could it be a bug? – Kipton Barros Aug 23 '11 at 23:22
  • @Kipton, it turns out `{ case ... }` can be syntactic sugar for a `x match { case ...}` anonymous function - see my answer. – huynhjl Aug 24 '11 at 03:36
  • @huynhjl, I understand that, as spec'ed, the compiler converts `{ case ... }` to a Function of the form `x => x match { case ... }`. The bizarre thing is that the compiler also does the converse: converts `x => x match { case ...}` to a PartialFunction. I'm pretty sure this is not in the spec, and a compiler bug. See my answer for more details. – Kipton Barros Aug 24 '11 at 03:40
  • @Kipton, yes I see what you mean now. – huynhjl Aug 24 '11 at 03:46
  • @Kipton Indeed, thanks for your comments and complementary information! – Jean-Philippe Pellet Aug 24 '11 at 09:27
  • 1
    `{ case ... }` can be both a partial function and a function -- it depends on the expected type. – Daniel C. Sobral Aug 24 '11 at 15:03
16

The language specification addresses that in section 8.5. The relevant portions:

An anonymous function can be defined by a sequence of cases

{ case p1 => b1 ... case pn => bn }

If the expected type is scala.Functionk[S1, ..., Sk, R] , the expression is taken to be equivalent to the anonymous function:

(x1 : S1, ..., xk : Sk) => (x1, ..., xk) match {
  case p1 => b1 ... case pn => bn
}

If the expected type is scala.PartialFunction[S, R], the expression is taken to be equivalent to the following instance creation expression:

new scala.PartialFunction[S, T ] {
  def apply(x: S): T = x match {
    case p1 => b1 ... case pn => bn
  }
  def isDefinedAt(x: S): Boolean = {
    case p1 => true ... case pn => true
    case _ => false
  }
}  

So typing the expression as PartialFunction or a Function influences how the expression is compiled.

Also trait PartialFunction [-A, +B] extends (A) ⇒ B so a partial function PartialFunction[A,B] is also a Function[A,B].

huynhjl
  • 41,520
  • 14
  • 105
  • 158
  • Yes, I think this answers the original question nicely. More info: when the expected type is a Function, as in the case of `Seq.filter`, this explains how `{ case ... }` gets used. For other methods, such as `Seq.collect`, the expected type is a PartialFunction, and then an expression `{ case ... }` *should* be required, rather than `x => x match { case ... }`; Jean-Philippe's answer handles that case, but note the strange compiler behavior described in my answer. – Kipton Barros Aug 24 '11 at 04:58
  • Going to go with this answer as the accepted answer because it covers what the compiler is doing pretty well. Thanks everybody for your answers. – Amir Raminfar Aug 24 '11 at 20:56
6

-- Revised post --

Hmm, I'm not sure I see a difference, Scala 2.9.1.RC3,

val f: PartialFunction[Int, Int] = { case 2 => 3 }
f.isDefinedAt(1) // evaluates to false
f.isDefinedAt(2) // evaluates to true
f(1) // match error

val g: PartialFunction[Int, Int] = x => x match { case 2 => 3 }
g.isDefinedAt(1) // evaluates to false
g.isDefinedAt(2) // evaluates to true
g(1) // match error

It seems f and g behave exactly the same as PartialFunctions.

Here's another example demonstrating the equivalence:

Seq(1, "a").collect(x => x match { case s: String => s }) // evaluates to Seq(a)

Even more interesting:

// this compiles
val g: PartialFunction[Int, Int] = (x: Int) => {x match { case 2 => 3 }}

// this fails; found Function[Int, Int], required PartialFunction[Int, Int]
val g: PartialFunction[Int, Int] = (x: Int) => {(); x match { case 2 => 3 }}

So there's some special casing at the compiler level to convert between x => x match {...} and just {...}.

Update. After reading the language spec, this seems like a bug to me. I filed SI-4940 in the bug tracker.

Kipton Barros
  • 21,002
  • 4
  • 67
  • 80