Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 {
}
like image 687
TheKojuEffect Avatar asked Jan 19 '16 15:01

TheKojuEffect


1 Answers

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).

like image 85
Travis Brown Avatar answered Sep 21 '22 00:09

Travis Brown