使用 Shapeless 通用默认实例构建 case 类,并使用定义通用 createValue 方法的参数
Build, using Shapeless, generic default instances for case classes with parameters defining a common createValue method
我正在努力实现以下目标 - 使用 shapeless 似乎是一个不错的途径。
给定当前 class 模型:
import shapeless._
object ShapelessTest {
case class Definition[T](id: String) extends Typeable[T] {
type V = Value[T]
override def cast(t: Any): Option[T] = createValue(t.asInstanceOf[Option[T]]).value
override def describe: String = s"$id"
def createValue(value: Option[T]): V =
Value[T](this, value)
}
case class Value[T](definition: Definition[T], value: Option[T])
val DefA: Definition[Int] = Definition[Int]("defA")
val DefB: Definition[String] = Definition[String]("defB")
case class Instance(valA: DefA.V,
valB: DefB.V)
def main(args: Array[String]): Unit = {
val Empty: Instance = Instance(
DefA.createValue(None),
DefB.createValue(None)
)
println(s"Empty manual: $Empty")
val emptyHl = Generic[Instance].from(DefA.createValue(None) :: DefB.createValue(None) :: HNil)
println(s"Empty hlist: $emptyHl")
}
}
我可以创建一个空实例作为 Empty
实例,方法是在正确的定义上手动调用 createValue
方法,或者使用 shapeless 转换 HList。
我想弄清楚是否有可能以编程方式为每个 class 具有类型 Value
.
的字段创建一个 Empty
实例
换句话说,我希望能够调用
val Empty: Instance = empty[Instance]
并且具有与 emptyHl
或 Empty
实例相同的结果。
这似乎类似于无形指南中的“8.3 随机值生成器”示例,但不是生成随机数,而是使用案例 class 中存在的每种类型的函数,我正在尝试具体化每个参数的具体 Definition
类型,并对其调用 createValue(None)
方法。
我试了很多都没有成功。
使用 hlist.Mapper
和在 Typeable
上定义的 Poly1
,我可以获得参数列表,但我无法调用任何方法在 typeable 上。
任何帮助将不胜感激,谢谢! :)
Update 9 Apr
我能够想出一个非常复杂的解决方案 - 不幸的是有很多铸造但完成了工作。
我想对此进行迭代并使其变得更好。我尝试使用 natMapper: NatTRel
但我无法让它在单例类型上工作。
我相信这可以做得更好!欢迎任何建议。
import shapeless.ops.hlist
import shapeless.ops.hlist.{Comapped, Reify}
import shapeless.{Generic, HList, HNil}
object ShapelessTest2 {
case class Definition[T](id: String) {
type V = Value[this.type]
def createValue(value: Option[T]) =
new Value[this.type] {
type NT = T
override val valueT: Option[T] = value
override val attrDef: Definition.this.type = Definition.this
}
}
trait Value[D] {
type NT
val attrDef: D
val valueT: Option[NT]
}
object DefA extends Definition[Int]("defA")
object DefB extends Definition[Int]("defB")
object DefC extends Definition[String]("defC")
case class Instance(valA: DefA.V,
valB: DefB.V,
valC: DefC.V)
// Compile safe
val Inst1: Instance = Instance(
DefA.createValue(Some(1)),
DefB.createValue(Some(2)),
DefC.createValue(Some("2"))
)
def main(args: Array[String]): Unit = {
def empty[A <: Product] = new PartiallyApplied[A]
class PartiallyApplied[A <: Product] {
def apply[
V <: HList,
DL <: HList,
RDL <: HList,
H <: Definition[_],
T <: HList
]()(
implicit
gen: Generic.Aux[A, V],
comapped: Comapped.Aux[V, Value, DL],
reify: Reify.Aux[DL, RDL],
isHCons: hlist.IsHCons.Aux[RDL, H, T],
): A = {
def getEmpties[L](list: RDL): V = {
val hlist = list match {
case HNil => HNil
case _ => list.head.createValue(None) :: getEmpties(list.tail.asInstanceOf[RDL])
}
hlist.asInstanceOf[V]
}
val empties = getEmpties(reify.apply())
gen.from(empties)
}
}
val emptyInstance = empty[Instance]()
println(s"Empty valA: ${emptyInstance.valA.attrDef} - ${emptyInstance.valA.valueT}")
println(s"Empty valB: ${emptyInstance.valB.attrDef} - ${emptyInstance.valB.valueT}")
println(s"Empty valC: ${emptyInstance.valC.attrDef} - ${emptyInstance.valC.valueT}")
}
}
正确打印
Empty valA: Definition(defA) - None
Empty valB: Definition(defB) - None
Empty valC: Definition(defC) - None
根本不用 Typeable
就可以更轻松地实现你想做的事情(顺便说一句,Typeable[T]
应该用作类型 class 而不是混合)。在 ggeneral 中,派生类型 classes with shapeless 的模式如下所示:
import shapeless._
trait Empty[A] {
def createValue(): A
}
object Empty {
// summoning method
def apply[T](implicit empty: Empty[T]): T = empty.createValue()
// implicits for HNil and HCons build HList if we provide the right implicits
implicit val emptyHNil: Empty[HNil] = () => HNil
implicit def emptyHCons[H, T](
implicit
head: Empty[H],
tail: Empty[T]
): Empty[H :: T] = () => head.createValue() :: tail.createValue()
// Generic will translate it HList into Product type
implicit def emptyProduct[T, Rep](
implicit
gen: Generic.Aux[T, Rep],
empty: Empty[Rep]
): Empty[T] = () => gen.from(empty.createValue())
// if you need default instances accessible everywhere without import put them here
}
用作
case class Instance(int: Int, string: String)
implicit int: Empty[Int] = () => 0
implicit int: Empty[String] = () => ""
Empty[Instance].createValue[Instance] // Instance(0, "")
在您的情况下,您必须使 Value[T]
s 在隐式范围内可用,并从较小的值派生 Value[Instance]
(如上面的 Empty
示例)。如何在隐式作用域中提供基本 Value
实例取决于您:您可以将它们放入伴随对象中,或者手动定义或从隐式作用域中已经存在的其他类型派生。
长话短说,你想要的应该是可能的,但你必须先将事情放入隐式范围。
我猜你不知何故滥用了 Typeable
。使用 Typeable
的想法是进行类型安全转换。但是你回到 asInstanceOf
.
Typeable
是一个类型 class。所以你应该使用你的 Definition
作为类型 class。使 DefA
、DefB
、...隐含。
implicit val DefA: Definition[Int] = Definition[Int]("defA")
implicit val DefB: Definition[String] = Definition[String]("defB")
def empty[A <: Product] = new PartiallyApplied[A]
class PartiallyApplied[A <: Product] {
def apply[Vs <: HList, L <: HList, Ds <: HList]()(implicit
gen: Generic.Aux[A, Vs],
comapped: Comapped.Aux[Vs, Value, L],
liftAll: LiftAll.Aux[Definition, L, Ds],
natMapper: NatTRel[Ds, Definition, Vs, Value],
): A = {
object createValueNatTransform extends (Definition ~> Value) {
override def apply[T](definition: Definition[T]): Value[T] =
definition.createValue(None)
}
gen.from(natMapper.map(createValueNatTransform, liftAll.instances))
}
}
val Empty: Instance = empty[Instance]()
// Instance(Value(Typeable[defA],None),Value(Typeable[defB],None))
使用您的原始代码的宏
def empty[A]: A = macro emptyImpl[A]
def emptyImpl[A: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
val typA = weakTypeOf[A]
val trees = typA.decls.filter(_.asTerm.isVal).map(_.infoIn(typA) match {
case TypeRef(pre, _, _) => q"${pre.termSymbol}.createValue(_root_.scala.None)"
})
q"new $typA(..$trees)"
}
val Empty: Instance = empty[Instance]
//Warning:scalac: performing macro expansion App.empty[App.Instance]
//Warning:scalac: new App.Instance(DefA.createValue(_root_.scala.None),
// DefB.createValue(_root_.scala.None))
// Instance(Value(Typeable[defA],None),Value(Typeable[defB],None))
关于您的新代码使 Value
协变并使用 Mapper
(对于正确定义的 Poly
)而不是 NatTRel
或运行时递归
trait Value[+D] {
type NT
val attrDef: D
val valueT: Option[NT]
}
object createValuePoly extends Poly1 {
implicit def cse[D <: Definition[T] with Singleton, T](implicit
ev: D <:< Definition[T]): Case.Aux[D, Value[D]] = at(_.createValue(None))
}
def empty[A <: Product] = new PartiallyApplied[A]
class PartiallyApplied[A <: Product] {
def apply[
V <: HList,
DL <: HList,
]()(
implicit
gen: Generic.Aux[A, V],
comapped: Comapped.Aux[V, Value, DL],
reify: Reify.Aux[DL, DL],
mapper: Mapper.Aux[createValuePoly.type, DL, V]
): A = gen.from(mapper(reify()))
}
val emptyInstance = empty[Instance]()
println(s"Empty valA: ${emptyInstance.valA.attrDef} - ${emptyInstance.valA.valueT}")
//Empty valA: Definition(defA) - None
println(s"Empty valB: ${emptyInstance.valB.attrDef} - ${emptyInstance.valB.valueT}")
//Empty valB: Definition(defB) - None
println(s"Empty valC: ${emptyInstance.valC.attrDef} - ${emptyInstance.valC.valueT}")
//Empty valC: Definition(defC) - None
我正在努力实现以下目标 - 使用 shapeless 似乎是一个不错的途径。
给定当前 class 模型:
import shapeless._
object ShapelessTest {
case class Definition[T](id: String) extends Typeable[T] {
type V = Value[T]
override def cast(t: Any): Option[T] = createValue(t.asInstanceOf[Option[T]]).value
override def describe: String = s"$id"
def createValue(value: Option[T]): V =
Value[T](this, value)
}
case class Value[T](definition: Definition[T], value: Option[T])
val DefA: Definition[Int] = Definition[Int]("defA")
val DefB: Definition[String] = Definition[String]("defB")
case class Instance(valA: DefA.V,
valB: DefB.V)
def main(args: Array[String]): Unit = {
val Empty: Instance = Instance(
DefA.createValue(None),
DefB.createValue(None)
)
println(s"Empty manual: $Empty")
val emptyHl = Generic[Instance].from(DefA.createValue(None) :: DefB.createValue(None) :: HNil)
println(s"Empty hlist: $emptyHl")
}
}
我可以创建一个空实例作为 Empty
实例,方法是在正确的定义上手动调用 createValue
方法,或者使用 shapeless 转换 HList。
我想弄清楚是否有可能以编程方式为每个 class 具有类型 Value
.
Empty
实例
换句话说,我希望能够调用
val Empty: Instance = empty[Instance]
并且具有与 emptyHl
或 Empty
实例相同的结果。
这似乎类似于无形指南中的“8.3 随机值生成器”示例,但不是生成随机数,而是使用案例 class 中存在的每种类型的函数,我正在尝试具体化每个参数的具体 Definition
类型,并对其调用 createValue(None)
方法。
我试了很多都没有成功。
使用 hlist.Mapper
和在 Typeable
上定义的 Poly1
,我可以获得参数列表,但我无法调用任何方法在 typeable 上。
任何帮助将不胜感激,谢谢! :)
Update 9 Apr
我能够想出一个非常复杂的解决方案 - 不幸的是有很多铸造但完成了工作。
我想对此进行迭代并使其变得更好。我尝试使用 natMapper: NatTRel
但我无法让它在单例类型上工作。
我相信这可以做得更好!欢迎任何建议。
import shapeless.ops.hlist
import shapeless.ops.hlist.{Comapped, Reify}
import shapeless.{Generic, HList, HNil}
object ShapelessTest2 {
case class Definition[T](id: String) {
type V = Value[this.type]
def createValue(value: Option[T]) =
new Value[this.type] {
type NT = T
override val valueT: Option[T] = value
override val attrDef: Definition.this.type = Definition.this
}
}
trait Value[D] {
type NT
val attrDef: D
val valueT: Option[NT]
}
object DefA extends Definition[Int]("defA")
object DefB extends Definition[Int]("defB")
object DefC extends Definition[String]("defC")
case class Instance(valA: DefA.V,
valB: DefB.V,
valC: DefC.V)
// Compile safe
val Inst1: Instance = Instance(
DefA.createValue(Some(1)),
DefB.createValue(Some(2)),
DefC.createValue(Some("2"))
)
def main(args: Array[String]): Unit = {
def empty[A <: Product] = new PartiallyApplied[A]
class PartiallyApplied[A <: Product] {
def apply[
V <: HList,
DL <: HList,
RDL <: HList,
H <: Definition[_],
T <: HList
]()(
implicit
gen: Generic.Aux[A, V],
comapped: Comapped.Aux[V, Value, DL],
reify: Reify.Aux[DL, RDL],
isHCons: hlist.IsHCons.Aux[RDL, H, T],
): A = {
def getEmpties[L](list: RDL): V = {
val hlist = list match {
case HNil => HNil
case _ => list.head.createValue(None) :: getEmpties(list.tail.asInstanceOf[RDL])
}
hlist.asInstanceOf[V]
}
val empties = getEmpties(reify.apply())
gen.from(empties)
}
}
val emptyInstance = empty[Instance]()
println(s"Empty valA: ${emptyInstance.valA.attrDef} - ${emptyInstance.valA.valueT}")
println(s"Empty valB: ${emptyInstance.valB.attrDef} - ${emptyInstance.valB.valueT}")
println(s"Empty valC: ${emptyInstance.valC.attrDef} - ${emptyInstance.valC.valueT}")
}
}
正确打印
Empty valA: Definition(defA) - None
Empty valB: Definition(defB) - None
Empty valC: Definition(defC) - None
根本不用 Typeable
就可以更轻松地实现你想做的事情(顺便说一句,Typeable[T]
应该用作类型 class 而不是混合)。在 ggeneral 中,派生类型 classes with shapeless 的模式如下所示:
import shapeless._
trait Empty[A] {
def createValue(): A
}
object Empty {
// summoning method
def apply[T](implicit empty: Empty[T]): T = empty.createValue()
// implicits for HNil and HCons build HList if we provide the right implicits
implicit val emptyHNil: Empty[HNil] = () => HNil
implicit def emptyHCons[H, T](
implicit
head: Empty[H],
tail: Empty[T]
): Empty[H :: T] = () => head.createValue() :: tail.createValue()
// Generic will translate it HList into Product type
implicit def emptyProduct[T, Rep](
implicit
gen: Generic.Aux[T, Rep],
empty: Empty[Rep]
): Empty[T] = () => gen.from(empty.createValue())
// if you need default instances accessible everywhere without import put them here
}
用作
case class Instance(int: Int, string: String)
implicit int: Empty[Int] = () => 0
implicit int: Empty[String] = () => ""
Empty[Instance].createValue[Instance] // Instance(0, "")
在您的情况下,您必须使 Value[T]
s 在隐式范围内可用,并从较小的值派生 Value[Instance]
(如上面的 Empty
示例)。如何在隐式作用域中提供基本 Value
实例取决于您:您可以将它们放入伴随对象中,或者手动定义或从隐式作用域中已经存在的其他类型派生。
长话短说,你想要的应该是可能的,但你必须先将事情放入隐式范围。
我猜你不知何故滥用了 Typeable
。使用 Typeable
的想法是进行类型安全转换。但是你回到 asInstanceOf
.
Typeable
是一个类型 class。所以你应该使用你的 Definition
作为类型 class。使 DefA
、DefB
、...隐含。
implicit val DefA: Definition[Int] = Definition[Int]("defA")
implicit val DefB: Definition[String] = Definition[String]("defB")
def empty[A <: Product] = new PartiallyApplied[A]
class PartiallyApplied[A <: Product] {
def apply[Vs <: HList, L <: HList, Ds <: HList]()(implicit
gen: Generic.Aux[A, Vs],
comapped: Comapped.Aux[Vs, Value, L],
liftAll: LiftAll.Aux[Definition, L, Ds],
natMapper: NatTRel[Ds, Definition, Vs, Value],
): A = {
object createValueNatTransform extends (Definition ~> Value) {
override def apply[T](definition: Definition[T]): Value[T] =
definition.createValue(None)
}
gen.from(natMapper.map(createValueNatTransform, liftAll.instances))
}
}
val Empty: Instance = empty[Instance]()
// Instance(Value(Typeable[defA],None),Value(Typeable[defB],None))
使用您的原始代码的宏
def empty[A]: A = macro emptyImpl[A]
def emptyImpl[A: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
val typA = weakTypeOf[A]
val trees = typA.decls.filter(_.asTerm.isVal).map(_.infoIn(typA) match {
case TypeRef(pre, _, _) => q"${pre.termSymbol}.createValue(_root_.scala.None)"
})
q"new $typA(..$trees)"
}
val Empty: Instance = empty[Instance]
//Warning:scalac: performing macro expansion App.empty[App.Instance]
//Warning:scalac: new App.Instance(DefA.createValue(_root_.scala.None),
// DefB.createValue(_root_.scala.None))
// Instance(Value(Typeable[defA],None),Value(Typeable[defB],None))
关于您的新代码使 Value
协变并使用 Mapper
(对于正确定义的 Poly
)而不是 NatTRel
或运行时递归
trait Value[+D] {
type NT
val attrDef: D
val valueT: Option[NT]
}
object createValuePoly extends Poly1 {
implicit def cse[D <: Definition[T] with Singleton, T](implicit
ev: D <:< Definition[T]): Case.Aux[D, Value[D]] = at(_.createValue(None))
}
def empty[A <: Product] = new PartiallyApplied[A]
class PartiallyApplied[A <: Product] {
def apply[
V <: HList,
DL <: HList,
]()(
implicit
gen: Generic.Aux[A, V],
comapped: Comapped.Aux[V, Value, DL],
reify: Reify.Aux[DL, DL],
mapper: Mapper.Aux[createValuePoly.type, DL, V]
): A = gen.from(mapper(reify()))
}
val emptyInstance = empty[Instance]()
println(s"Empty valA: ${emptyInstance.valA.attrDef} - ${emptyInstance.valA.valueT}")
//Empty valA: Definition(defA) - None
println(s"Empty valB: ${emptyInstance.valB.attrDef} - ${emptyInstance.valB.valueT}")
//Empty valB: Definition(defB) - None
println(s"Empty valC: ${emptyInstance.valC.attrDef} - ${emptyInstance.valC.valueT}")
//Empty valC: Definition(defC) - None