反向HList并转换为类?

问题描述

我正在使用Shapeless将Akka中的物化值累积为HList,并将其转换为case类。

(您不必对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类之前先反转列表,如下所示:

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]): 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]
  ): 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,r0: Reverse.Aux[R,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] ): A = g.from(l.reverse) } toCaseClass[Result](b :: a :: HNil) ,因为r0在通话L时就已经知道)

如果需要,您可以将匿名类替换为命名类

.apply(...)

或者,您可以定义一个类型类(尽管这有点麻烦)

def toCaseClass[A] = new PartiallyApplied[A]

class PartiallyApplied[A] {
  def 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案例研究:案例类迁移)

请注意,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,L <: HList](implicit g: Generic.Aux[A,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) 使用一个单个类型的参数。

您使用IceCreamV1("Sundae",1,true).migrateTo[IceCreamV2a]编写的代码由于多种原因而无法正常工作。

首先,GraphOps不仅仅是包装。这是基于宏的类型类to handle“发散隐式扩展”(在Scala 2.13中有by-name shapeless.Lazy隐式,尽管它们不等同于{{1} })。当您了解需要使用=>时应该使用{1>}。

第二,您似乎定义了一些隐式转换(隐式视图,Lazy),但是隐式转换的解析比其他隐式(1 2 3的解析要复杂。 4 5)。

第三,您似乎以为在定义时

Lazy

Mat => Aimplicit def foo: Foo = ??? def useImplicitFoo(implicit foo1: Foo) = ??? 。但是通常这是不正确的。 foo1在当前范围内定义,foo将在foo呼叫站点范围内解决:

Setting abstract type based on typeclass

When doing implicit resolution with type parameters,why does val placement matter?foo1useImplicitFoo之间的差异)

因此,当您调用implicit x: X时,隐式implicitly[X]不在范围内。

代码的固定版本为

createConverter

尝试

toCaseClass