akka 是否反转HList并转换为类?

ygya80vv  于 2022-11-06  发布在  其他
关注(0)|答案(1)|浏览(161)

我使用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)

我需要指定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 }

这有点像是“无形”的反向推理;我不能很好地链接抽象类型成员。
如果你在这里有洞察力,深深的感谢。
我的错:上面的例子,当然,需要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的演示编译器。)

fnx2tebb

fnx2tebb1#

如果我没理解错的话你希望

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类型参数,在调用.apply()时不能推断L,因为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)

(here我们不需要r0,因为L在调用.apply(...)时已经变为已知的)。
如果需要,可以将匿名类替换为命名类

def toCaseClass[A] = new PartiallyApplied[A]

class PartiallyApplied[A] {
  def apply...
}

或者,您可以定义一个类型类(尽管这样做有点罗嗦)

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)

使用类型类隐藏几个隐式表达式: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)。
第三,你似乎假设当你定义

implicit def foo: Foo = ???

def useImplicitFoo(implicit foo1: Foo) = ???

foo1foo,但一般情况下不是这样的,foo是在当前作用域中定义的,foo1将在useImplicitFoo调用站点的作用域中解析:
Setting abstract type based on typeclass
When doing implicit resolution with type parameters, why does val placement matter?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]

相关问题