反向HList并转换为类? [英] Reverse HList and convert to class?

查看:48
本文介绍了反向HList并转换为类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

(您不必对Akka知道太多的问题,但是默认方法将物化值累积为递归嵌套的2元组,这并不有趣,因此Shapeless HLists似乎是一种更明智的方法-并且可以工作很好,但是我不知道如何正确地重用这种方法.在这里,我将简化Akka产生的价值类型.)

例如,假设我们有两种实体化类型,"A"和"B":

 案例类Result(b:B,a:A)创建一个.mapMaterialized(((a:A)=> a :: HNil).viaMat(flowCreatingB)(((list1,b:B)=> b :: list1).mapMaterialized(list2 =>通用[Result] .from(list2))//list1 = A :: HNil//list2 = B :: A :: HNil 

...并产生 Result 就好了.但这要求您的案例类要向后写入-首先是值的末尾,以此类推-这有点笨拙且难以遵循.

所以明智的做法是在转换为case类之前先反转列表,如下所示:

 案例类Result(a:A,b:B)//....mapMaterialized(list2 =>通用[Result] .from(list2.reverse)) 

现在,我们可以按照构建它们的顺序来考虑 Result 属性.是的.

但是如何简化和重用这一行代码?

问题在于隐式对多个类型参数不起作用.例如:

  def toCaseClass [A,R<:HList](隐含g:Generic.Aux [A,R],r:Reverse.Aux [L,R]):R =>A =l =>g.来自(l.reverse) 

我需要同时指定 A (上面的 Result )和正在构建的HList:

  .mapMaterialized(toCaseClass [Result,B :: A :: HNil]) 

显然,长列表将使该调用变得荒谬(Akka倾向于建立看起来非常丑陋的实体化类型,而不仅仅是"A"和"B").最好写这样的东西:

  .mapMaterialized(toCaseClass [Result]) 

我尝试使用隐式方法解决此问题,如下所示:

 隐式类GraphOps [Mat< ;: HList](g:RunnableGraph [Mat]){隐式def createConverter [A,RL< ;: HList](隐式r:Reverse.Aux [Mat,RL],gen:Generic.Aux [A,RL]):Lazy [Mat =>A] =懒惰的{l =>val x:RL = l.反向值y:A = gen.from(x)gen.from(l.reverse)}def toCaseClass [A](隐式转换:Lazy [Mat => A]):RunnableGraph [A] = {g.mapMaterializedValue(convert.value)} 

但是编译器抱怨没有可用的隐式视图".

更深层的问题是我不太了解如何正确推断...

 //R =倒序(例如B :: A :: NHNil)//T =要创建的类型(例如Result(a,b))//H = H T的清单(例如A :: B :: HNil)gen:Generic.Aux [T,H]//Generic [T] {type Repr = H}rev:Reverse.Aux [R,H]//Reverse [R] {类型Out = H} 

这是Shapeless喜欢推断事物的方式的倒退.我不能完全正确地链接抽象类型成员.

如果您对此有深入的了解,请深表感谢.


我的糟糕:上面的示例当然需要Akka进行编译.一个简单的方法是这样(感谢Dymtro的帮助):

 导入无形状._导入shapeless.ops.hlist.Reverse案例类Result(一个:字符串,两个:Int)val结果= 2 ::一个":: HNilprintln(Generic [Result] .from(results.reverse))//这有效:打印"Result(one,2)";案例类Converter [A,B](值:A => B)隐式类Ops [L< ;: HList](列表:L){隐式def createConverter [A,RL< ;: HList](隐式r:Reverse.Aux [L,RL],gen:Generic.Aux [A,RL]):Converter [L,A] =转换器(l => gen.from(l.reverse))def toClass [A](隐式转换器:Converter [L,A]):A =converter.value(列表)}println(results.toClass [Result])//错误:找不到参数转换器的隐式值://Converter [Int :: String :: shapeless.HNil,Result] 


Dymtro的最后一个例子,在下面...

 隐式类GraphOps [Mat< ;: HList,R< ;: HList](g:RunnableGraph [Mat]){def toCaseClass [A](隐式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](隐式g:Generic.Aux [A,R],r:Reverse.Aux [L,R]):L =>A = 1 =>.g.来自(l.reverse) 

您只能指定 A ,然后才能推断出 R L .

您可以使用 PartiallyApplied 模式

  import shapeless.ops.hlist.Reverse导入无形.{Generic,HList,HNil}def toCaseClass [A] = new {def apply [R< ;: HList,L< ;: HList]()(隐式g:Generic.Aux [A,R],r0:Reverse.Aux [R,L],r:Reverse.Aux [L,R]):L =>A = 1 => 1.g.来自(l.reverse)}A级B级val a =新Aval b =新B案例类Result(a:A,b:B)toCaseClass [Result]().apply(b :: a :: HNil) 

(没有隐式的 r0 类型参数 L 不能在调用 .apply()时进行推断,因为 L .apply().apply(...))

时才知道

或更好

  def toCaseClass [A] =新{def apply [R< ;: HList,L< ;: HList](l:L)(隐式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] =新的PartiallyApplied [A]类PartiallyApplied [A] {def适用...} 

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

  trait ToCaseClass [A] {L型def toCaseClass(l:L):A}对象ToCaseClass {类型Aux [A,L0] = ToCaseClass [A] {类型L = L0}def instance [A,L0](f:L0 => A):Aux [A,L0] = new ToCaseClass [A] {类型L = L0覆盖def toCaseClass(l:L0):A = f(l)}隐式def mkToCaseClass [A,R< ;: HList,L< ;: HList](隐式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](隐式tcc:ToCaseClass [A]):tcc.L =>A = tcc.toCaseClasstoCaseClass [Result] .apply(b :: a :: HNil) 

使用类型类隐藏多个隐式: https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:ops:migration (6.3案例研究:案例类迁移)

请注意, IceCreamV1("Sundae",1,true).migrateTo [IceCreamV2a] 使用单个类型参数.

您使用 GraphOps 编写的代码由于某些原因而无法正常工作.

首先, shapeless.Lazy 不仅仅是包装.这是基于宏的类型类 Scala 2.13 中,有1 3 ).

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

 隐式def foo:Foo = ???def useImplicitFoo(隐式foo1:Foo)= ??? 

foo1 foo .但是通常这是不正确的. foo 在当前范围内定义,并且 foo1 将在 useImplicitFoo 调用站点的范围内解析:

基于类型类设置抽象类型

使用隐式解析时类型参数,为什么val放置很重要?( implicit x:X implicitly [X] 之间的差异)

因此,当您调用 toCaseClass 时,隐式 createConverter 不在范围内.

代码的固定版本为

  trait RunnableGraph [Mat] {def mapMaterializedValue [A](a:Mat => A):RunnableGraph [A]}案例类Wrapper [A,B](值:A => B)隐式类GraphOps [Mat< ;: HList](g:RunnableGraph [Mat]){val ops =这个隐式def createConverter [A,RL< ;: HList](隐式r:Reverse.Aux [Mat,RL],gen:Generic.Aux [A,RL],):包装器[Mat,A] =包装器{l =>val x:RL = l.反向值y:A = gen.from(x)gen.from(l.reverse)}def toCaseClass [A](隐式转换:Wrapper [Mat,A]):RunnableGraph [A] = {g.mapMaterializedValue(convert.value)}}val g:RunnableGraph [B :: A :: HNil] = ???val ops = g.ops导入操作_g.toCaseClass [结果] 


尝试

 导入akka.stream.scaladsl.RunnableGraph导入shapeless.{::,Generic,HList,HNil}导入shapeless.ops.hlist.Reverse隐式类GraphOps [Mat< ;: HList,R< ;: HList](g:RunnableGraph [Mat]){def toCaseClass [A](隐式r:Reverse.Aux [Mat,R],gen:Generic.Aux [A,R]):RunnableGraph [A] = g.mapMaterializedValue(l => gen.from(l.reverse))}案例类Result(一个:字符串,两个:Int)val g:RunnableGraph [Int :: String :: HNil] = ???g.toCaseClass [结果] 

I'm using Shapeless to accumulate materialized values in Akka as an HList and convert that to a case class.

(You don't have to know Akka much for this question, but the default approach accumulates materialized values as recursively nested 2-tuples, which isn't much fun, so Shapeless HLists seemed a more sensible approach -- and works pretty well. But I don't know how to properly re-use that approach. Here, I'll simplify the kinds of values Akka produces.)

For example, let's say we've got two materialized types, "A" and "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

... and that produces Result just fine. But it requires that your case class be written backwards -- first value last, etc -- which is kind of dorky and hard to follow.

So the sensible thing is to reverse the list before converting to the case class, like this:

case class Result(a: A, b: B)
// ...
  .mapMaterialized(list2 => Generic[Result].from(list2.reverse))

Now we can think about Result properties in the same order they were built. Yay.

But how to simplify and reuse this line of code?

The problem is that implicits don't work on multiple type parameters. For example:

def toCaseClass[A, R <: HList](implicit g: Generic.Aux[A, R], r: Reverse.Aux[L, R]): R => A =
  l => g.from(l.reverse) 

I'd need to specify both A (Result, above) and the HList being built:

  .mapMaterialized(toCaseClass[Result, B :: A :: HNil])

Obviously, that invocation is going to be absurd with long lists (and Akka tends to build up really ugly-looking materialized types, not merely "A" and "B"). It'd be nicer to write something like:

  .mapMaterialized(toCaseClass[Result])

I've tried to solve this using implicits, like this:

  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)
    }

But the compiler complains "No implicit view available".

The deeper problem is that I don't quite understand how to properly infer...

// 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 }

This is sort of backwards from how Shapeless likes to infer things; I can't quite chain the abstract type members properly.

Profound thanks if you have insight here.


My bad: the example above, of course, requires Akka to compile. A simpler way of putting it is this (with thanks to 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's final example, below...

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)) 
}

... does seem to do what I'd been hoping for. Thank you very much Dmytro!

(Note: I had been somewhat misled in analyzing it earlier: it seems IntelliJ's presentation compiler incorrectly insists it won't compile (missing implicits). Moral: Don't trust IJ's presentation compiler.)

解决方案

If I understood correctly you wish that in

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)

you could specify only A and then R, L be inferred.

You can do this with PartiallyApplied 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)

(without implicit r0 type parameter L can't be inferred upon call of .apply() because L becomes known only upon call .apply().apply(...))

or better

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 we don't need r0 because L becomes known already upon call .apply(...)).

If you want you can replace anonymous class with named one

def toCaseClass[A] = new PartiallyApplied[A]

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

Alternatively you can define a type class (although this is a little more wordy)

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)

Hiding several implicits with a type class: How to wrap a method having implicits with another method in Scala?

You could find an answer to your question in Type Astronaut:

https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:ops:migration (6.3 Case study: case class migrations)

Notice that IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2a] takes a single type parameter.

Your code with GraphOps doesn't work for several reasons.

Firstly, shapeless.Lazy is not just a wrapper. It's a macro-based type class to handle "diverging implicit expansion" (in Scala 2.13 there are by-name => implicits for that, although they are not equivalent to Lazy). You should use Lazy when you understand why you need it.

Secondly, you seem to define some implicit conversion (implicit view, Mat => A) but resolution of implicit conversions is trickier than resolution of other implicits (1 2 3 4 5).

Thirdly, you seem to assume that when you define

implicit def foo: Foo = ???

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

foo1 is foo. But generally this is not true. foo is defined in current scope and foo1 will be resolved in the scope of useImplicitFoo call site:

Setting abstract type based on typeclass

When doing implicit resolution with type parameters, why does val placement matter? (difference between implicit x: X and implicitly[X])

So implicit createConverter is just not in scope when you call toCaseClass.

Fixed version of your code is

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]

这篇关于反向HList并转换为类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆