Why Scala PartialFunction works without defining isDefinedAt?

257 Views Asked by At

It looks First and Second are the same, but why?

First

val iter = List(1, 2, 3, 4, 5).iterator
val first = iter.collect(new PartialFunction[Int, Int]{
  def apply(i: Int) = i
  def isDefinedAt(i: Int) = i > 0 && i < 3
})
first.foreach((println(_)))

Second

val iter2 = List(1, 2, 3, 4, 5).iterator
val second = iter2.collect {
  case i:Int if i > 0 && i < 3 => i
}
second.foreach((println(_)))

Is it because the Scala compiler automatically converts { case i:Int if i > 0 && i < 3 => i } into the implelentation form of First with generating isDefinedAt from if i > 0 && i < 3 part?

Also, case i:Int if i > 0 && i < 3 => i is Case class pattern matching, if I am correct. However, in scala/src/library/scala/PartialFunction.scala, there is no Case class definition for PartialFunction.

trait PartialFunction[-A, +B] extends (A => B)

Then why this case class pattern match works?

I suppose Scala compiler does lots of implicit works intelligently but it confuses me to understand what is happening and how to write Scala code.

If there are good references, instead of language or compiler specifications, to understand Scala code syntax and Scala way of writing code, please suggest.

3

There are 3 best solutions below

1
Alexey Romanov On BEST ANSWER

Is it because the Scala compiler automatically converts { case i:Int if i > 0 && i < 3 => i } into the implelentation form of First with generating isDefinedAt from **if i > 0 && i < 3 ** part?

Yes, the exact translation is given in Pattern Matching Anonymous Functions. Here it'll be

new PartialFunction[Int, Int]{
  def apply(x: Int) = x match {
    case i:Int if i > 0 && i < 3 => i
  }
  def isDefinedAt(x: Int) = x match {
    case i:Int if i > 0 && i < 3 => true
    case _ => false
  } 
}

Note the difference with your first example in apply! You can still call it when isDefined is false.

Also, case i:Int if i > 0 && i < 3 => i is Case class pattern matching, if I am correct

If anything, it's the other way around; case classes are called that way because they can be pattern-matched and pattern matching uses case keyword in Scala.

0
Tim On

Yes, the compiler converts the second version into a PartialFunction[Int,Int] (because that is what collect takes).

There is no case class matching here, and it is not even matching on type because the value must be Int (and therefore the type declaration in the second version is not required).

The style guide gives lots on tips on how Scala is typically written.

0
som-snytt On

For your example

object Main {
  def f = (1 to 5).collect { case i if i > 0 && i < 3 => i }
}

The compiler-generated partial function defines applyOrElse because it is more efficient than the naive idiom:

if (pf.isDefinedAt(x)) pf.apply(x) else ???

Showing that implementation, which is similar to what is described in the spec:

$ scalac -Vprint:typer pf.scala
[[syntax trees at end of                     typer]] // pf.scala
package <empty> {
  object Main extends scala.AnyRef {
    def <init>(): Main.type = {
      Main.super.<init>();
      ()
    };
    def f: IndexedSeq[Int] = scala.Predef.intWrapper(1).to(5).collect[Int](({
      @SerialVersionUID(value = 0) final <synthetic> class $anonfun extends scala.runtime.AbstractPartialFunction[Int,Int] with java.io.Serializable {
        def <init>(): <$anon: Int => Int> = {
          $anonfun.super.<init>();
          ()
        };
        final override def applyOrElse[A1 <: Int, B1 >: Int](x1: A1, default: A1 => B1): B1 = ((x1.asInstanceOf[Int]: Int): Int @unchecked) match {
          case (i @ _) if i.>(0).&&(i.<(3)) => i
          case (defaultCase$ @ _) => default.apply(x1)
        };
        final def isDefinedAt(x1: Int): Boolean = ((x1.asInstanceOf[Int]: Int): Int @unchecked) match {
          case (i @ _) if i.>(0).&&(i.<(3)) => true
          case (defaultCase$ @ _) => false
        }
      };
      new $anonfun()
    }: PartialFunction[Int,Int]))
  }
}

where AbstractPartialFunction defines

def apply(x: T1): R = applyOrElse(x, PartialFunction.empty)

Here is an external link to a change to use applyOrElse. The improved PartialFunction dates back to 2012. Probably the feature is under-documented or under-advertised. Some information is available by expanding the Scaladoc for PartialFunction. For some reason, that link shows orElse, so you'd actually have to scroll back for applyOrElse. It seems documentation is hard.