反转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) 

我需要指定 AResult,上面)和正在构建的 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,然后RL被推断。

您可以使用 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) = ???

foo1foo。但通常情况并非如此。 foo 在当前范围内定义,foo1 将在 useImplicitFoo 调用站点的范围内解析:

implicit x: Ximplicitly[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]