Given:
case object A
What's the difference, if any, between the @
and :
in:
def f(a: A.type): Int = a match {
case aaa @ A => 42
}
and
def f(a: A.type): Int = a match {
case aaa : A.type => 42
}
Given:
case object A
What's the difference, if any, between the @
and :
in:
def f(a: A.type): Int = a match {
case aaa @ A => 42
}
and
def f(a: A.type): Int = a match {
case aaa : A.type => 42
}
The first one @
uses an extractor to do the pattern matching while the second one :
requires the type - that's why you need to pass in A.type
there.
There's actually no difference between them in terms of matching. To better illustrate the difference between @
and :
we can look at a simple class, which doesn't provide an extractor out of the box.
class A
def f(a: A) = a match {
case _ : A => // works fine
case _ @ A => // doesn't compile because no extractor is found
}
In this very specific case, almost nothing is different. They will both achieve the same results.
Semantically, case aaa @ A => 42
is usage of pattern binding where we're matching on the exact object A
, and case aaa : A.type => 42
is a type pattern where we want a
to have the type A.type
. In short, type versus equality, which doesn't make a difference for a singleton.
The generated code is actually slightly different. Consider this code compiled with -Xprint:patmat
:
def f(a: A.type): Int = a match {
case aaa @ A => 42
case aaa : A.type => 42
}
The relevant code for f
shows that the two cases are slightly different, but will not produce different results:
def f(a: A.type): Int = {
case <synthetic> val x1: A.type = a;
case6(){
if (A.==(x1)) // case aaa @ A
matchEnd5(42)
else
case7()
};
case7(){
if (x1.ne(null)) // case aaa: A.type
matchEnd5(42)
else
case8()
};
case8(){
matchEnd5(throw new MatchError(x1))
};
matchEnd5(x: Int){
x
}
}
The first case checks equality, where the second case only checks that the reference is not null
(we already know the type matches since the method parameter is the singleton type).
Semantically, there is no difference in this case. We can have a look at the bytecode to see if there is a runtime difference:
> object A
defined object A
> object X { def f(a: A.type) = a match { case a @ A => 42 } }
defined object X
> :javap X
...
public int f($line4.$read$$iw$$iw$$iw$$iw$$iw$$iw$A$);
descriptor: (L$line4/$read$$iw$$iw$$iw$$iw$$iw$$iw$A$;)I
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=2
0: aload_1
1: astore_3
2: getstatic #51 // Field $line4/$read$$iw$$iw$$iw$$iw$$iw$$iw$A$.MODULE$:L$line4/$read$$iw$$iw$$iw$$iw$$iw$$iw$A$;
5: aload_3
6: invokevirtual #55 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
9: ifeq 18
12: bipush 42
14: istore_2
15: goto 30
18: goto 21
21: new #57 // class scala/MatchError
24: dup
25: aload_3
26: invokespecial #60 // Method scala/MatchError."<init>":(Ljava/lang/Object;)V
29: athrow
30: iload_2
31: ireturn
And the other case:
> object Y { def f(a: A.type) = a match { case a: A.type => 42 } }
defined object Y
> :javap Y
...
public int f($line4.$read$$iw$$iw$$iw$$iw$$iw$$iw$A$);
descriptor: (L$line4/$read$$iw$$iw$$iw$$iw$$iw$$iw$A$;)I
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=2
0: aload_1
1: astore_3
2: aload_3
3: ifnull 12
6: bipush 42
8: istore_2
9: goto 24
12: goto 15
15: new #50 // class scala/MatchError
18: dup
19: aload_3
20: invokespecial #53 // Method scala/MatchError."<init>":(Ljava/lang/Object;)V
23: athrow
24: iload_2
25: ireturn
Indeed, there is a small difference. In the second case the compiler can see that a parameter of type A.type
has only two values: A.type
and null
. Therefore at runtime there is only a check whether it is null
because the other case is checked at compile time. In the first version of the code, the compiler doesn't do this optimization. Instead it is calling the equals
method.
If we change the type of the parameter slightly, we get a different result:
> object Z { def f(a: AnyRef) = a match { case a: A.type => 42 } }
defined object Z
> :javap Z
...
public int f(java.lang.Object);
descriptor: (Ljava/lang/Object;)I
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=2
0: aload_1
1: astore_3
2: aload_3
3: getstatic #51 // Field $line4/$read$$iw$$iw$$iw$$iw$$iw$$iw$A$.MODULE$:L$line4/$read$$iw$$iw$$iw$$iw$$iw$$iw$A$;
6: if_acmpne 15
9: bipush 42
11: istore_2
12: goto 27
15: goto 18
18: new #53 // class scala/MatchError
21: dup
22: aload_3
23: invokespecial #56 // Method scala/MatchError."<init>":(Ljava/lang/Object;)V
26: athrow
27: iload_2
28: ireturn
In this version the compiler no longer knows what the parameter is, therefore it is doing a comparison of the types at runtime. We could now discuss whether the call of equals
in the first version or the type comparison in the third call is more efficient but I guess the JIT of the JVM is optimizing away any overhead in both cases anyway, therefore we first would have to look at the machine code to tell which code is more efficient, if there is a difference at all.
Semantically there is no different in this particular example but in general we include keyword @ if we want to do something with the object itself. This thread explains the use of these extractors with a simple example.