我使用Shapeless在Akka中将物化值累积为一个HList,并将其转换为一个case类。
(You对于这个问题,我不必太了解Akka,但是默认的方法将物化值累积为递归嵌套的2元组,这并不有趣,因此Shapeless HList似乎是一种更明智的方法--而且工作得很好。但是我不知道如何正确地重用这种方法。在这里,我将简化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类之前反转列表,如下所示:
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 }
这有点像是“无形”的反向推理;我不能很好地链接抽象类型成员。
如果你在这里有洞察力,深深的感谢。
我的错:上面的例子,当然,需要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],
gen: Generic.Aux[A, 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],
gen: Generic.Aux[A, R]
): RunnableGraph[A] = g.mapMaterializedValue(l => gen.from(l.reverse))
}
......似乎确实做到了我所希望的。非常感谢你,德米特罗!
(Note我之前的分析有些误导。看起来IntelliJ的表示编译器错误地坚持它不会编译(缺少隐含)。不要相信IJ的演示编译器。)
1条答案
按热度按时间fnx2tebb1#
如果我没理解错的话你希望
您只能指定
A
,然后推断出R
、L
。您可以使用PartiallyApplied模式执行此操作
(没有隐式
r0
类型参数,在调用.apply()
时不能推断L
,因为L
只有在调用.apply().apply(...)
时才变为已知)或更好
(here我们不需要
r0
,因为L
在调用.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案例研究:案例类迁移)
请注意,
IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2a]
接受一个单一类型参数。您的
GraphOps
代码由于以下几个原因无法工作。首先,
shapeless.Lazy
不仅仅是一个 Package 器,它是一个基于宏的类型类,用来处理“发散隐式扩展”(在Scala 2.13中有by-name=>
隐式表示,尽管它们不等价于Lazy
)。其次,您似乎定义了一些隐式转换(隐式视图,
Mat => A
),但隐式转换的解析比其他隐式转换的解析更棘手(12345)。第三,你似乎假设当你定义
foo1
是foo
,但一般情况下不是这样的,foo
是在当前作用域中定义的,foo1
将在useImplicitFoo
调用站点的作用域中解析:Setting abstract type based on typeclass
When doing implicit resolution with type parameters, why does val placement matter?(
implicit x: X
与implicitly[X]
之间的差值)所以当你调用
toCaseClass
时,隐式的createConverter
不在作用域中。代码的修复版本为
尝试