问题描述
我正在使用Shapeless将Akka中的物化值累积为HList,并将其转换为case类。
(您不必对Akka知道太多的问题,但是默认方法会以递归嵌套的2元组的形式累积物化值,这并不是很有趣,因此Shapeless HLists似乎是一种更明智的方法-并且可以工作很好,但是我不知道如何正确地重用这种方法。在这里,我将简化Akka产生的价值。)
例如,假设我们有两个实体化类型,“ A”和“ B”:
case class Result(b: B,a: A)
createA
.mapMaterialized((a: A) => a :: HNil)
.viaMat(flowCreatingB)((list1,b: B) => b :: list1)
.mapMaterialized(list2 => Generic[Result].from(list2))
// list1 = A :: HNil
// list2 = B :: A :: HNil
...这样就可以产生Result
了。但这要求您的案例类要向后写入-首先是值的最后一个,以此类推-这有点笨拙且难以遵循。
所以明智的做法是在转换为case类之前先反转列表,如下所示:
case class Result(a: A,b: B)
// ...
.mapMaterialized(list2 => Generic[Result].from(list2.reverse))
现在,我们可以按照构建它们的顺序来考虑Result
属性。是的。
但是如何简化和重用这一行代码?
问题在于隐式对多个类型参数不起作用。例如:
def toCaseClass[A,R <: HList](implicit g: Generic.Aux[A,R],r: Reverse.Aux[L,R]): R => A =
l => g.from(l.reverse)
我需要同时指定A
(上面的Result
)和要建立的HList:
.mapMaterialized(toCaseClass[Result,B :: A :: HNil])
很明显,长长的列表将使调用变得荒谬(Akka倾向于建立看起来非常丑陋的实体化类型,而不仅仅是“ A”和“ B”)。像这样写会更好:
.mapMaterialized(toCaseClass[Result])
implicit class GraphOps[Mat <: HList](g: RunnableGraph[Mat]) {
implicit def createConverter[A,RL <: HList](implicit
r: Reverse.Aux[Mat,RL],gen: Generic.Aux[A,RL]): Lazy[Mat => A] =
Lazy { l =>
val x: RL = l.reverse
val y: A = gen.from(x)
gen.from(l.reverse)
}
def toCaseClass[A](implicit convert: Lazy[Mat => A]): RunnableGraph[A] = {
g.mapMaterializedValue(convert.value)
}
但是编译器抱怨“没有可用的隐式视图”。
更深层的问题是我不太了解如何正确推断...
// R = Reversed order (e.g. B :: A :: NHNil)
// T = Type to create (e.g. Result(a,b))
// H = HList of T (e.g. A :: B :: HNil)
gen: Generic.Aux[T,H] // Generic[T] { type Repr = H }
rev: Reverse.Aux[R,H] // Reverse[R] { type Out = H }
这有点类似于Shapeless喜欢推断事物的方式。我不能完全正确地链接抽象类型成员。
如果您对此有深入的了解,深表感谢。
我的糟糕:上面的示例当然需要Akka进行编译。一个简单的方法是这样(感谢Dymtro的帮助):
import shapeless._
import shapeless.ops.hlist.Reverse
case class Result(one: String,two: Int)
val results = 2 :: "one" :: HNil
println(Generic[Result].from(results.reverse))
// this works: prints "Result(one,2)"
case class Converter[A,B](value: A => B)
implicit class Ops[L <: HList](list: L) {
implicit def createConverter[A,RL <: HList](implicit
r: Reverse.Aux[L,RL]): Converter[L,A] =
Converter(l => gen.from(l.reverse))
def toClass[A](implicit converter: Converter[L,A]): A =
converter.value(list)
}
println(results.toClass[Result])
// error: Could not find implicit value for parameter converter:
// Converter[Int :: String :: shapeless.HNil,Result]
Dymtro的最后一个例子,如下...
implicit class GraphOps[Mat <: HList,R <: HList](g: RunnableGraph[Mat]) {
def toCaseClass[A](implicit
r: Reverse.Aux[Mat,R]
): RunnableGraph[A] = g.mapMaterializedValue(l => gen.from(l.reverse))
}
...似乎确实可以满足我的期望。非常感谢Dmytro!
(注意:我之前在分析它时被误导了:似乎IntelliJ的演示文稿编译器错误地坚持认为它不会编译(缺少隐式)。道德:不要相信IJ的演示文稿编译器。)
解决方法
如果我理解正确,那么您希望
def toCaseClass[A,R <: HList,L <: HList](implicit
g: Generic.Aux[A,R],r: Reverse.Aux[L,R]
): L => A = l => g.from(l.reverse)
您只能指定A
,然后才能推断出R
,L
。
您可以使用PartiallyApplied模式进行操作
import shapeless.ops.hlist.Reverse
import shapeless.{Generic,HList,HNil}
def toCaseClass[A] = new {
def apply[R <: HList,L <: HList]()(implicit
g: Generic.Aux[A,r0: Reverse.Aux[R,L],R]
): L => A = l => g.from(l.reverse)
}
class A
class B
val a = new A
val b = new B
case class Result(a: A,b: B)
toCaseClass[Result]().apply(b :: a :: HNil)
(没有隐式r0
类型参数L
的调用就无法推断,因为.apply()
仅在调用L
时才知道)>
或更好
.apply().apply(...)
(这里不需要def toCaseClass[A] = new {
def apply[R <: HList,L <: HList](l: L)(implicit
g: Generic.Aux[A,R]
): A = g.from(l.reverse)
}
toCaseClass[Result](b :: a :: HNil)
,因为r0
在通话L
时就已经知道)
如果需要,您可以将匿名类替换为命名类
.apply(...)
或者,您可以定义一个类型类(尽管这有点麻烦)
def toCaseClass[A] = new PartiallyApplied[A]
class PartiallyApplied[A] {
def apply...
}
用类型类How to wrap a method having implicits with another method in Scala?
隐藏多个隐式对象您可以在类型宇航员中找到问题的答案:
https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:ops:migration(6.3案例研究:案例类迁移)
请注意,trait ToCaseClass[A] {
type L
def toCaseClass(l: L): A
}
object ToCaseClass {
type Aux[A,L0] = ToCaseClass[A] { type L = L0 }
def instance[A,L0](f: L0 => A): Aux[A,L0] = new ToCaseClass[A] {
type L = L0
override def toCaseClass(l: L0): A = f(l)
}
implicit def mkToCaseClass[A,L <: HList](implicit
g: Generic.Aux[A,R]
): Aux[A,L] = instance(l => g.from(l.reverse))
}
def toCaseClass[A](implicit tcc: ToCaseClass[A]): tcc.L => A = tcc.toCaseClass
toCaseClass[Result].apply(b :: a :: HNil)
使用一个单个类型的参数。
您使用IceCreamV1("Sundae",1,true).migrateTo[IceCreamV2a]
编写的代码由于多种原因而无法正常工作。
首先,GraphOps
不仅仅是包装。这是基于宏的类型类to handle“发散隐式扩展”(在Scala 2.13中有by-name shapeless.Lazy
隐式,尽管它们不等同于{{1} })。当您了解需要使用=>
时应该使用{1>}。
第二,您似乎定义了一些隐式转换(隐式视图,Lazy
),但是隐式转换的解析比其他隐式(1 2 3的解析要复杂。 4 5)。
第三,您似乎以为在定义时
Lazy
Mat => A
是implicit def foo: Foo = ???
def useImplicitFoo(implicit foo1: Foo) = ???
。但是通常这是不正确的。 foo1
在当前范围内定义,foo
将在foo
呼叫站点范围内解决:
Setting abstract type based on typeclass
When doing implicit resolution with type parameters,why does val placement matter?(foo1
和useImplicitFoo
之间的差异)
因此,当您调用implicit x: X
时,隐式implicitly[X]
不在范围内。
代码的固定版本为
createConverter
尝试
toCaseClass