1
votes

I was looking at a scala project on github to learn scala when i came across the Case1 snippet. A trait extends the Scala Product trait and then a case object extends that trait. This compiles just fine. To understand this better i tried out Case2 which does not seem to compile and it asks me to define the absMethod() in the case object. I cannot understand why this does not happen in the first case.

//Case 1
sealed abstract trait test1 extends Product with Serializable
case object test11 extends test1
test11.productArity  

//Case 2  
trait abstact extends Any{
  def absMethod():Int
}  
sealed abstract trait test2 extends abstact
case object test22 extends test2
test22.absMethod()
2
Thanks for the question, I posted an answer that will hopefully clarify your doubt. Aside from that, a couple of notes on your code: you do not have to extends Any explicitly (a class that extends "nothing" implicitly extends Any) and you don't have to annotate your trait as abstract. - stefanobaghino
That's not correct. When a class or trait or object doesn't explicitly extend anything it automatically extends AnyRef. If you have a class extending AnyVal and want it to inherit from a trait, that trait needs to extend Any explicitly. - Jasper-M

2 Answers

5
votes

The first case compiles because the compiler has special knowledge of productArity for case classes/objects. As part of the compilation process, it will not only create a companion object (for case classes), but also implement several methods, such as equals, hashCode, productArity (and more).

If you look at the output of your first test after the typer phase (scalac -Xprint:typer):

sealed abstract trait test1 extends AnyRef with Product with Serializable;
  case object test11 extends AnyRef with com.github.yuvalitzchakov.TupleTest.test1 with Product with Serializable {
    def <init>(): com.github.yuvalitzchakov.TupleTest.test11.type = {
      test11.super.<init>();
      ()
    };
    override <synthetic> def productPrefix: String = "test11";
    <synthetic> def productArity: Int = 0;
    <synthetic> def productElement(x$1: Int): Any = x$1 match {
      case _ => throw new IndexOutOfBoundsException(x$1.toString())
    };
    override <synthetic> def productIterator: Iterator[Any] = scala.runtime.ScalaRunTime.typedProductIterator[Any](test11.this);
    <synthetic> def canEqual(x$1: Any): Boolean = x$1.$isInstanceOf[com.github.yuvalitzchakov.TupleTest.test11.type]();
    override <synthetic> def hashCode(): Int = -877171150;
    override <synthetic> def toString(): String = "test11";
    <synthetic> private def readResolve(): Object = com.github.yuvalitzchakov.TupleTest.test11
}

You can see how the compiler implemented productArity and assigns 0 (since an object has no constructor arguments), which was abstract on Product. In the case of your custom defined abstract method, you have to fill that on your own, hence the compiler complains when it doesn't find an implementation.

1
votes

That happens because the case object is concrete and needs to have an implementation of its abstract parent class. For example, this will work and print 42:

trait Abstract {
  def method(): Int
}  

sealed trait Test2 extends Abstract

case object Test22 extends Test2 {
  def method(): Int = 42
}

Test22.method()

For the first case, case classes and case objects are Products and the fields for them are generated at compile time. For example, a case class with two fields is also a Product with arity 2, just like a Tuple2[A, B] ((A, B), a pair).