在 scala 中通过 case class 定义要扩展的特征
Define a trait to be extended by case class in scala
我有一些案例 classes 在其伴生对象中定义了一个方法 tupled
。从companion objects下面的代码可以看出,只是代码重复。
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)来看,似乎我们不能将特征的自我类型强制为一个案例class。
有没有办法定义一个特征(比如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 {
}
因为 Scala 中的 FunctionN
类型之间没有关系,如果没有 arity-level 某处的样板文件,就不可能做到这一点——只是没有办法对伴随对象的 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 个成员的情况 类(因为伴随对象上的 apply
方法不能 eta-expanded 到一个函数,如果有超过22 个参数)。
问题在于 apply
的签名因情况而异,并且这些函数没有共同特征。 Book.tupled
和 Author.tupled
基本上具有相同的代码,但具有非常不同的签名。因此,解决方案可能没有我们想要的那么好。
我可以想到一种使用注释宏来剪切样板的方法。由于没有使用标准库的好方法,我将求助于代码生成(它仍然具有 compile-time 安全性)。这里需要注意的是注释宏需要使用 macro paradise 编译器插件。宏也必须在一个单独的编译单元中(就像另一个 sbt sub-project)。使用注释的代码还需要使用宏天堂插件。
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>
或者,对于 shapeless 来说,这样的事情可能是可能的。请参阅@TravisBrown 的更好答案。
我有一些案例 classes 在其伴生对象中定义了一个方法 tupled
。从companion objects下面的代码可以看出,只是代码重复。
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)来看,似乎我们不能将特征的自我类型强制为一个案例class。
有没有办法定义一个特征(比如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 {
}
因为 Scala 中的 FunctionN
类型之间没有关系,如果没有 arity-level 某处的样板文件,就不可能做到这一点——只是没有办法对伴随对象的 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 个成员的情况 类(因为伴随对象上的 apply
方法不能 eta-expanded 到一个函数,如果有超过22 个参数)。
问题在于 apply
的签名因情况而异,并且这些函数没有共同特征。 Book.tupled
和 Author.tupled
基本上具有相同的代码,但具有非常不同的签名。因此,解决方案可能没有我们想要的那么好。
我可以想到一种使用注释宏来剪切样板的方法。由于没有使用标准库的好方法,我将求助于代码生成(它仍然具有 compile-time 安全性)。这里需要注意的是注释宏需要使用 macro paradise 编译器插件。宏也必须在一个单独的编译单元中(就像另一个 sbt sub-project)。使用注释的代码还需要使用宏天堂插件。
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>
或者,对于 shapeless 来说,这样的事情可能是可能的。请参阅@TravisBrown 的更好答案。