scalatraitscase-classself-type

Define a trait to be extended by case class in scala


I have some case classes which have a method tupled defined in its companion object. As it can be seen from the code below in companion objects, it is just code duplication.

case class Book(id: Int, isbn: String, name: String)

object Book {
  def tupled = (Book.apply _).tupled // Duplication
}


case class Author(id: Int, name: String)

object Author {
  def tupled = (Author.apply _).tupled // Duplication
}

From another question (can a scala self type enforce a case class type), it seems like we can not enforce the self-type of a trait to be a case class.

Is there a way to define a trait (say Tupled) that can be applied as following?

// What would be value of ???
trait Tupled {
  self: ??? =>

  def tupled = (self.apply _).tupled
}

// Such that I can replace tupled definition with Trait
object Book extends Tupled {
}

Solution

  • Because there's no relationship between FunctionN types in Scala, it's not possible to do this without arity-level boilerplate somewhere—there's just no way to abstract over the companion objects' apply methods without enumerating all the possible numbers of members.

    You could do this by hand with a bunch of CompanionN[A, B, C, ...] traits, but that's pretty annoying. Shapeless provides a much better solution, which allows you to write something like the following:

    import shapeless.{ Generic, HList }, shapeless.ops.product.ToHList
    
    class CaseClassCompanion[C] {
      def tupled[P <: Product, R <: HList](p: P)(implicit
        gen: Generic.Aux[C, R],
        toR: ToHList.Aux[P, R]
      ): C = gen.from(toR(p))
    }
    

    And then:

    case class Book(id: Int, isbn: String, name: String)
    object Book extends CaseClassCompanion[Book]
    
    case class Author(id: Int, name: String)
    object Author extends CaseClassCompanion[Author]
    

    Which you can use like this:

    scala> Book.tupled((0, "some ISBN", "some name"))
    res0: Book = Book(0,some ISBN,some name)
    
    scala> Author.tupled((0, "some name"))
    res1: Author = Author(0,some name)
    

    You might not even want the CaseClassCompanion part, since it's possible to construct a generic method that converts tuples to case classes (assuming the member types line up):

    class PartiallyAppliedProductToCc[C] {
      def apply[P <: Product, R <: HList](p: P)(implicit
        gen: Generic.Aux[C, R],
        toR: ToHList.Aux[P, R]
      ): C = gen.from(toR(p))
    }
    
    def productToCc[C]: PartiallyAppliedProductToCc[C] =
      new PartiallyAppliedProductToCc[C]
    

    And then:

    scala> productToCc[Book]((0, "some ISBN", "some name"))
    res2: Book = Book(0,some ISBN,some name)
    
    scala> productToCc[Author]((0, "some name"))
    res3: Author = Author(0,some name)
    

    This will work for case classes with up to 22 members (since the apply method on the companion object can't be eta-expanded to a function if there are more than 22 arguments).