Scala’s PartialFunction trait contains two abstract methods:isDefiendAt, apply.
Example implementation:
val divide = new PartialFunction[Int, Int] {
def apply(x: Int) = 42 / x
def isDefinedAt(x: Int) = x != 0
}
Another implementation is as follows:
val divide2: PartialFunction[Int, Int] = {
case d: Int if d != 0 => 42 / d
}
How does Scala maps second implementation to those two abstract methods?
A partial function implemented via a (usually non-exhaustive) pattern match will use the pattern match for both methods.
-
isDefinedAt
returns true, if a pattern matches.
-
apply
simply executes the pattern match normally on the input
The compiler will also generate an efficient applyOrElse
implementation, which avoids evaluating the pattern twice, once for checking definedness and once for applying.
Note that your two implementations are not identical: divide(0)
will result in an ArithmeticException
because of the zero division, while divide2(0)
will result in a MatchError
. In this case, where both throw an error, this may not be a critical difference, but if you e.g. had used Double
instead of Int
, your first implementation would return Infinity
, while the second still would throw an error.
In most cases, where a PartialFunction
is expected, the isDefinedAt
is checked, but as PartialFunction
is a subtype of Function
in Scala, you should take care when passing it to methods expecting the latter.
1 Like
If you’re interested in the implementation details, again, javap holds all the answers.
C:\Users\marti\scala\indy>type test.scala
class Example {
val partialExample: PartialFunction[Int, Int] = {
case d: Int if guard(d) => processor(d)
}
def guard(i: Int) = i != 0
def processor(i: Int) = 42 / 0
}
C:\Users\marti\scala\indy>scalac test.scala
C:\Users\marti\scala\indy>"C:\Program Files\Java\jdk1.8.0_191\bin\javap.exe" -c -p Example.class
Compiled from "test.scala"
public class Example {
private final scala.PartialFunction<java.lang.Object, java.lang.Object> partialExample;
public scala.PartialFunction<java.lang.Object, java.lang.Object> partialExample();
Code:
0: aload_0
1: getfield #17 // Field partialExample:Lscala/PartialFunction;
4: areturn
public boolean guard(int);
Code:
0: iload_1
1: iconst_0
2: if_icmpeq 7
5: iconst_1
6: ireturn
7: iconst_0
8: ireturn
public int processor(int);
Code:
0: bipush 42
2: iconst_0
3: idiv
4: ireturn
public Example();
Code:
0: aload_0
1: invokespecial #29 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #10 // class Example$$anonfun$1
8: dup
9: aload_0
10: invokespecial #32 // Method Example$$anonfun$1."<init>":(LExample;)V
13: putfield #17 // Field partialExample:Lscala/PartialFunction;
16: return
}
"C:\Program Files\Java\jdk1.8.0_191\bin\javap.exe" -c -p Example$$anonfun$1.class
Compiled from "test.scala"
public final class Example$$anonfun$1 extends scala.runtime.AbstractPartialFunction$mcII$sp implements scala.Serializable {
public static final long serialVersionUID;
private final Example $outer;
public final <A1, B1> B1 applyOrElse(A1, scala.Function1<A1, B1>);
Code:
0: iload_1
1: istore 4
3: iload 4
5: istore 5
7: aload_0
8: getfield #22 // Field $outer:LExample;
11: iload 5
13: invokevirtual #26 // Method Example.guard:(I)Z
16: ifeq 35
19: aload_0
20: getfield #22 // Field $outer:LExample;
23: iload 5
25: invokevirtual #30 // Method Example.processor:(I)I
28: invokestatic #36 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
31: astore_3
32: goto 46
35: aload_2
36: iload_1
37: invokestatic #36 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
40: invokeinterface #42, 2 // InterfaceMethod scala/Function1.apply:(Ljava/lang/Object;)Ljava/lang/Object;
45: astore_3
46: aload_3
47: areturn
public final boolean isDefinedAt(int);
Code:
0: iload_1
1: istore_3
2: iload_3
3: istore 4
5: aload_0
6: getfield #22 // Field $outer:LExample;
9: iload 4
11: invokevirtual #26 // Method Example.guard:(I)Z
14: ifeq 22
17: iconst_1
18: istore_2
19: goto 24
22: iconst_0
23: istore_2
24: iload_2
25: ireturn
public final boolean isDefinedAt(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: invokestatic #55 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
5: invokevirtual #57 // Method isDefinedAt:(I)Z
8: ireturn
public final java.lang.Object applyOrElse(java.lang.Object, scala.Function1);
Code:
0: aload_0
1: aload_1
2: invokestatic #55 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
5: aload_2
6: invokevirtual #61 // Method applyOrElse:(ILscala/Function1;)Ljava/lang/Object;
9: areturn
public Example$$anonfun$1(Example);
Code:
0: aload_1
1: ifnonnull 6
4: aconst_null
5: athrow
6: aload_0
7: aload_1
8: putfield #22 // Field $outer:LExample;
11: aload_0
12: invokespecial #66 // Method scala/runtime/AbstractPartialFunction$mcII$sp."<init>":()V
15: return
}
what you see here is that isDefinedAt
calls the guard method (plus ceremony), but not the processor method.
Conspicuously absent is the Apply
method, which is just inherited from Function1
, and just calls the entire thing (including guards).
That means that if (isDefinedAt(x)) apply(x) else doSomethingElse
will call the guard twice, once for the isDefinedAt
, and once for the apply
Do note there is also an applyOrElse
method, that does the same thing, but will only evaluate the guard once.
2 Likes