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