反转HList并转换为class?
Reverse HList and convert to class?
我正在使用 Shapeless 在 Akka 中将物化值累积为 HList 并将其转换为大小写 class。
(对于这个问题,你不必非常了解 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
就好了。但它要求您的案例 class 倒写——第一个值最后,等等——这有点笨拙且难以理解。
所以明智的做法是在转换为 class 大小写之前反转列表,如下所示:
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],
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))
}
... 似乎确实实现了我一直希望的结果。非常感谢 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, R],
r0: Reverse.Aux[R, L],
r: Reverse.Aux[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],
r: Reverse.Aux[L, R]
): A = g.from(l.reverse)
}
toCaseClass[Result](b :: a :: HNil)
(这里我们不需要 r0
因为 L
在调用 .apply(...)
时已经知道了)。
如果您愿意,可以将匿名 class 替换为具名
def toCaseClass[A] = new PartiallyApplied[A]
class PartiallyApplied[A] {
def apply...
}
或者你可以定义一个类型class(虽然这有点罗嗦)
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, R <: HList, L <: HList](implicit
g: Generic.Aux[A, R],
r0: Reverse.Aux[R, L],
r: Reverse.Aux[L, 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)
用类型 class 隐藏多个隐式:
您可以在 Type Astronaut:
中找到问题的答案
https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:ops:migration(6.3 案例研究:案例 class 迁移)
请注意 IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2a]
采用 单个 类型参数。
您使用 GraphOps
的代码由于多种原因无法正常工作。
首先,shapeless.Lazy
不仅仅是一个包装器。这是一个 macro-based 类型 class to handle "diverging implicit expansion" (in Scala 2.13 there are by-name =>
隐含的,尽管它们不等同于 Lazy
)。当你明白为什么需要它时,你应该使用 Lazy
。
其次,您似乎定义了一些隐式转换(隐式视图,Mat => A
)但是隐式转换的解析比其他隐式的解析更棘手( 3 5)。
第三,你似乎假设当你定义
implicit def foo: Foo = ???
def useImplicitFoo(implicit foo1: Foo) = ???
foo1
是 foo
。但通常情况并非如此。 foo
在当前范围内定义,foo1
将在 useImplicitFoo
调用站点的范围内解析:
(implicit x: X
和implicitly[X]
的区别)
当您调用 toCaseClass
.
时,隐含的 createConverter
不在范围内
您的代码的固定版本是
trait RunnableGraph[Mat]{
def mapMaterializedValue[A](a: Mat => A): RunnableGraph[A]
}
case class Wrapper[A, B](value: A => B)
implicit class GraphOps[Mat <: HList](g: RunnableGraph[Mat]) {
val ops = this
implicit def createConverter[A, RL <: HList](implicit
r: Reverse.Aux[Mat, RL],
gen: Generic.Aux[A, RL],
): Wrapper[Mat, A] =
Wrapper { l =>
val x: RL = l.reverse
val y: A = gen.from(x)
gen.from(l.reverse)
}
def toCaseClass[A](implicit convert: Wrapper[Mat, A]): RunnableGraph[A] = {
g.mapMaterializedValue(convert.value)
}
}
val g: RunnableGraph[B :: A :: HNil] = ???
val ops = g.ops
import ops._
g.toCaseClass[Result]
尝试
import akka.stream.scaladsl.RunnableGraph
import shapeless.{::, Generic, HList, HNil}
import shapeless.ops.hlist.Reverse
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))
}
case class Result(one: String, two: Int)
val g: RunnableGraph[Int :: String :: HNil] = ???
g.toCaseClass[Result]
我正在使用 Shapeless 在 Akka 中将物化值累积为 HList 并将其转换为大小写 class。
(对于这个问题,你不必非常了解 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
就好了。但它要求您的案例 class 倒写——第一个值最后,等等——这有点笨拙且难以理解。
所以明智的做法是在转换为 class 大小写之前反转列表,如下所示:
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],
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))
}
... 似乎确实实现了我一直希望的结果。非常感谢 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, R],
r0: Reverse.Aux[R, L],
r: Reverse.Aux[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],
r: Reverse.Aux[L, R]
): A = g.from(l.reverse)
}
toCaseClass[Result](b :: a :: HNil)
(这里我们不需要 r0
因为 L
在调用 .apply(...)
时已经知道了)。
如果您愿意,可以将匿名 class 替换为具名
def toCaseClass[A] = new PartiallyApplied[A]
class PartiallyApplied[A] {
def apply...
}
或者你可以定义一个类型class(虽然这有点罗嗦)
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, R <: HList, L <: HList](implicit
g: Generic.Aux[A, R],
r0: Reverse.Aux[R, L],
r: Reverse.Aux[L, 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)
用类型 class 隐藏多个隐式:
您可以在 Type Astronaut:
中找到问题的答案https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:ops:migration(6.3 案例研究:案例 class 迁移)
请注意 IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2a]
采用 单个 类型参数。
您使用 GraphOps
的代码由于多种原因无法正常工作。
首先,shapeless.Lazy
不仅仅是一个包装器。这是一个 macro-based 类型 class to handle "diverging implicit expansion" (in Scala 2.13 there are by-name =>
隐含的,尽管它们不等同于 Lazy
)。当你明白为什么需要它时,你应该使用 Lazy
。
其次,您似乎定义了一些隐式转换(隐式视图,Mat => A
)但是隐式转换的解析比其他隐式的解析更棘手(
第三,你似乎假设当你定义
implicit def foo: Foo = ???
def useImplicitFoo(implicit foo1: Foo) = ???
foo1
是 foo
。但通常情况并非如此。 foo
在当前范围内定义,foo1
将在 useImplicitFoo
调用站点的范围内解析:
implicit x: X
和implicitly[X]
的区别)
当您调用 toCaseClass
.
createConverter
不在范围内
您的代码的固定版本是
trait RunnableGraph[Mat]{
def mapMaterializedValue[A](a: Mat => A): RunnableGraph[A]
}
case class Wrapper[A, B](value: A => B)
implicit class GraphOps[Mat <: HList](g: RunnableGraph[Mat]) {
val ops = this
implicit def createConverter[A, RL <: HList](implicit
r: Reverse.Aux[Mat, RL],
gen: Generic.Aux[A, RL],
): Wrapper[Mat, A] =
Wrapper { l =>
val x: RL = l.reverse
val y: A = gen.from(x)
gen.from(l.reverse)
}
def toCaseClass[A](implicit convert: Wrapper[Mat, A]): RunnableGraph[A] = {
g.mapMaterializedValue(convert.value)
}
}
val g: RunnableGraph[B :: A :: HNil] = ???
val ops = g.ops
import ops._
g.toCaseClass[Result]
尝试
import akka.stream.scaladsl.RunnableGraph
import shapeless.{::, Generic, HList, HNil}
import shapeless.ops.hlist.Reverse
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))
}
case class Result(one: String, two: Int)
val g: RunnableGraph[Int :: String :: HNil] = ???
g.toCaseClass[Result]