我有一些案例类,其伴随对象中定义了方法tupled
。从下面的代码对象中可以看出,它只是代码重复。
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
}
从另一个问题(can a scala self type enforce a case class type)来看,我们似乎无法强制将特质的自我类型作为案例类。
有没有办法定义一个可以应用如下的特征(比如Tupled
)?
// 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 {
}
答案 0 :(得分:12)
因为Scala中的FunctionN
类型之间没有关系,所以如果没有arity级别的样板,就不可能做到这一点 - 没有办法在不枚举所有方法的情况下对伴随对象的apply
方法进行抽象可能的成员人数。
你可以用一堆CompanionN[A, B, C, ...]
特征手工完成这个,但这很烦人。 Shapeless提供了一个更好的解决方案,允许您编写如下内容:
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))
}
然后:
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]
您可以这样使用:
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)
您可能甚至不想要CaseClassCompanion
部分,因为可以构造一个将元组转换为案例类的通用方法(假设成员类型对齐):
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]
然后:
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)
这适用于最多包含22个成员的案例类(因为如果有超过22个参数,则无法将伴随对象上的apply
方法扩展为函数。)
答案 1 :(得分:2)
问题是apply
的签名因具体情况而异,并且这些功能没有共同特征。 Book.tupled
和Author.tupled
基本上具有相同的代码,但签名却截然不同。因此,解决方案可能不如我们所希望的那么好。
我可以想象一种使用注释宏来切割样板的方法。由于使用标准库没有很好的方法,我将使用代码生成(仍具有编译时安全性)。需要注意的是,注释宏需要使用macro paradise编译器插件。宏也必须位于单独的编译单元中(如另一个sbt子项目)。使用注释的代码也需要使用宏天堂插件。
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
@compileTimeOnly("enable macro paradise to expand macro annotations")
class Tupled extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro tupledMacroImpl.impl
}
object tupledMacroImpl {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val result = annottees map (_.tree) match {
// A case class with companion object, we insert the `tupled` method into the object
// and leave the case class alone.
case (classDef @ q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }")
:: (objDef @ q"object $objName extends { ..$objEarlyDefs } with ..$objParents { $objSelf => ..$objDefs }")
:: Nil if mods.hasFlag(Flag.CASE) =>
q"""
$classDef
object $objName extends { ..$objEarlyDefs } with ..$objParents { $objSelf =>
..$objDefs
def tupled = ($objName.apply _).tupled
}
"""
case _ => c.abort(c.enclosingPosition, "Invalid annotation target: must be a companion object of a case class.")
}
c.Expr[Any](result)
}
}
用法:
@Tupled
case class Author(id: Int, name: String)
object Author
// Exiting paste mode, now interpreting.
defined class Author
defined object Author
scala> Author.tupled
res0: ((Int, String)) => Author = <function1>
或者,这样的事情可能是无形的。请参阅@ TravisBrown的更好答案。