反转 HList 并转换为类?

2024-02-17

我使用 Shapeless 在 Akka 中累积物化值作为 HList 并将其转换为案例类。

(对于这个问题,您不必了解 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 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)) 
}

...似乎确实达到了我的期望。非常感谢德米特罗!

(注意:我之前在分析它时受到了一些误导: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可以推断。

你可以这样做部分应用 https://stackoverflow.com/search?q=%5Bscala%5D%20PartiallyApplied pattern

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(...)).

如果您愿意,可以将匿名类替换为命名类

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)

使用类型类隐藏多个隐式:如何在 Scala 中用另一个方法包装具有隐式方法的方法? https://stackoverflow.com/questions/59175012/how-to-wrap-a-method-having-implicits-with-another-method-in-scala/

您可以在以下位置找到问题的答案类型 宇航员:

https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:ops:migration https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:ops:migration(6.3 案例研究:案例类迁移)

请注意IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2a]需要一个single类型参数。

你的代码与GraphOps由于多种原因不起作用。

首先,shapeless.Lazy不仅仅是一个包装器。它是一个基于宏的类型类处理 https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#first-class-lazy-values-tie-implicit-recursive-knots“发散的隐式扩张”(在斯卡拉2.13 https://github.com/scala/scala/releases/tag/v2.13.0by-name https://docs.scala-lang.org/sips/byname-implicits.html =>隐含的,尽管它们不等于Lazy)。你应该使用Lazy当你明白为什么你需要它时。

其次,您似乎定义了一些隐式转换(隐式视图,Mat => A)但是隐式转换的解析比其他隐式转换的解析更棘手(1 https://stackoverflow.com/questions/63964610/implicit-view-not-working-is-my-implicit-def-to-blame 2 https://stackoverflow.com/questions/62630439/in-scala-are-there-any-condition-where-implicit-view-wont-be-able-to-propagate 3 https://stackoverflow.com/questions/62205940/when-calling-a-scala-function-with-compile-time-macro-how-to-failover-smoothly 4 https://stackoverflow.com/questions/62751493/scala-kleisli-throws-an-error-in-intellij 5 https://stackoverflow.com/questions/63002466/what-are-the-hidden-rules-regarding-the-type-inference-in-resolution-of-implicit).

第三,您似乎假设当您定义时

implicit def foo: Foo = ???

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

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

根据类型类设置抽象类型 https://stackoverflow.com/questions/64238219/setting-abstract-type-based-on-typeclass

当使用类型参数进行隐式解析时,为什么 val 放置很重要? https://stackoverflow.com/questions/59391232/when-doing-implicit-resolution-with-type-parameters-why-does-val-placement-matt(之间的差异implicit x: X and implicitly[X])

如此含蓄createConverter只是不在您调用时的范围内toCaseClass.

您的代码的固定版本是

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]

Try

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]
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

反转 HList 并转换为类? 的相关文章

随机推荐