Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Case Class Tupled

How can I call the tupled method on this case class?

case class(a: Int, b: String)(c: String, d: Int)

The reason why I have my case class like this is because I want to use only the first two parameters to be considered for equals and hashCode comparison!

So how can I properly call the tupled on such a case class?

like image 639
joesan Avatar asked May 25 '15 07:05

joesan


1 Answers

In short it does not seem like a good idea to use case classes this way. Here is an explanation.

Let's check the class declaration together with generated apply and unapply:

scala> case class A(a: Int, b: String)(c: String, d: Int)
defined class A

scala> A.apply _
res0: (Int, String) => (String, Int) => A = <function2>

scala> A.unapply _
res1: A => Option[(Int, String)] = <function1>

You can see that although apply takes 4 arguments in total (curried), unapply discards second list.

Let's see if we can access members of the second list:

scala> val a = A(1, "two")("three", 4)
a: A = A(1,two)

scala> a.a
res2: Int = 1

scala> a.c
<console>:11: error: value c is not a member of A
              a.c
                ^

... nope, not a regular way. Let's check couple more properties:

scala> a.productArity
res4: Int = 2

scala> a.productIterator.toList
res5: List[Any] = List(1, two)

Ok, it seems like second argument list is pretty much ignored. Let's dig in:

scala> :javap A
...
  public int a();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #16                 // Field a:I
         4: ireturn
...
  public java.lang.String b();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #21                 // Field b:Ljava/lang/String;
         4: areturn
...
  public boolean equals(java.lang.Object);
    descriptor: (Ljava/lang/Object;)Z
    flags: ACC_PUBLIC
     ... //mentions only a and b:....
        32: invokevirtual #32                 // Method a:()I
        35: aload         4
        37: invokevirtual #32                 // Method a:()I
        40: if_icmpne     88
        43: aload_0
        44: invokevirtual #35                 // Method b:()Ljava/lang/String;
        47: aload         4
        49: invokevirtual #35                 // Method b:()Ljava/lang/String;
...
  public A(int, java.lang.String, java.lang.String, int);                                                                 
    descriptor: (ILjava/lang/String;Ljava/lang/String;I)V                                                                 
    flags: ACC_PUBLIC                                                                                                     
    Code:                                                                                                                 
      stack=2, locals=5, args_size=5                                                                                      
         0: aload_0                                                                                                       
         1: iload_1                                                                                                       
         2: putfield      #16                 // Field a:I                                                                
         5: aload_0                                                                                                       
         6: aload_2                                                                                                       
         7: putfield      #21                 // Field b:Ljava/lang/String;                                               
        10: aload_0                                                                                                       
        11: invokespecial #100                // Method java/lang/Object."<init>":()V                                     
        14: aload_0                                                                                                       
        15: invokestatic  #106                // Method scala/Product$class.$init$:(Lscala/Product;)V                     
        18: return                                                                                                        
      LocalVariableTable:                                                                                                 
        Start  Length  Slot  Name   Signature                                                                             
            0      19     0  this   LA;                                                                                   
            0      19     1     a   I                                                                                     
            0      19     2     b   Ljava/lang/String;                                                                    
            0      19     3     c   Ljava/lang/String;                                                                    
            0      19     4     d   I                                                                                  

So there is no use of c and d in constructor or in equals.

You can still make second arg list params useful by prefixing them with val:

scala> case class B(a: Int, b: String)(val c: String, val d: Int)         
defined class B                                                           

scala> val b = B(1, "two")("three", 4)                                    
b: B = B(1,two)                                                           

scala> b.c                                                                
res6: String = three                                  

scala> b.d                                                                
res8: Int = 4    

Let's see how equality and hashcode works in this case:

scala> val b2 = B(1, "two")("no the same", 555)
b2: B = B(1,two)

scala> b == b2
res10: Boolean = true

scala> b.hashCode
res13: Int = -1563217100

scala> b2.hashCode
res14: Int = -1563217100

Seems to work the way you want it to, which I personally don't like ;)

For completeness, default pattern matching is still the way it was for class A:

scala> B.unapply _
res15: B => Option[(Int, String)] = <function1>

Scala language spec explains how it works here.

The formal parameters in the first parameter section of a case class are called elements; they are treated specially. First, the value of such a parameter can be extracted as a field of a constructor pattern. Second, a val prefix is implicitly added to such a parameter, unless the parameter carries already a val or var modifier. Hence, an accessor definition for the parameter is generated.

and

Every case class implicitly overrides some method definitions of class scala.AnyRef unless a definition of the same method is already given in the case class itself or a concrete definition of the same method is given in some base class of the case class different from AnyRef. In particular:

  • Method equals: (Any)Boolean is structural equality, where two instances are equal if they both belong to the case class in question and they have equal (with respect to equals) constructor arguments (restricted to the class's elements, i.e., the first parameter section).
  • Method hashCode: Int computes a hash-code. If the hashCode methods of the data structure members map equal (with respect to equals) values to equal hash-codes, then the case class hashCode method does too.
  • Method toString: String returns a string representation which contains the name of the class and its elements.
like image 64
yǝsʞǝla Avatar answered Nov 03 '22 01:11

yǝsʞǝla