无形不能在通用环境中工作 [英] Shapeless not working in generic context

查看:104
本文介绍了无形不能在通用环境中工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我仍然试图让我的头脑变得无形(并且程度稍差,Scala!),并且我一直在编写一些简单代码来为案例类生成随机实例数据 - 主要基于这里的指南: http://enear.github.io/2016/09/27 / bits-of-shapeless-2 / (这个例子涵盖了一个JSON编写器实现)



我创建了一个 Generator [A ] trait并为简单类型创建了隐式实现,并且按照上述链接中的示例,我还创建了隐式实现来处理HList,HNil,Coproduct和CNil:

  implicit def hnilGenerator = new Generator [HNil] {
override def generate(a:HNil)= HNil
}

隐式def hconsGenerator [H,T <:HList](隐式headGen:Generator [H],tailGen:Generator [T])=
new Generator [H :: T] {
重写def generate(a:H :: T )= headGen.generate(a.head):: tailGen.generate(a.tail)
}

隐式def cnilGenerator:Generator [CNil] =
new Generator [CNil ] {
覆盖def generate(a:CNil):CNil =抛出新的RuntimeException(无效候选配置)
}

隐式def cconsGenerator [H,T< ;: (:无效的候选配置)
}
$ / code>

现在我可以使用这段代码来生成一个基于案例类或密封特征的随机实例: / b>

 它(应该使用case类来hlist){
case class Test(x:IntGene,y :DoubleGene,z:BooleanGene)
val c = Generic [Test] .to(Test(IntGene(),DoubleGene(),BooleanGene()))
generate(c)
}
it(密封特质会发生什么){
密封特质Shape
case class Square (width:Int,height:Int)extends Shape
case class Circle(radius:Int)extends Shape

val c = Generic [Shape] .to(Circle(1))
generate(c)
}

然而,上述两项工作都没有问题,如果我试图使这是一个通用的(如在参数类型中),我得到的编译错误无法找到必要的暗示:

 它(应该处理泛型){
case class GenericTest [A:Generic](x:A){
def convert()= {
val c = Generic [A]。 to(x)
generate(c)
}
}
}

因此,从我的理解来看,因为我已经使用了通用上下文绑定 A ,编译器知道这将是可用的,所以 c 必须是从调用到(x)的一些可能的返回值。在实现中缺少某些东西来处理来自 Generic 无形调用的返回类型?或者我疯狂地误解了一些东西?



我希望这是可能的,我错过了一些东西 - 是编译器不知道什么会传入我假设没有),还是有另一种可能的类型需要隐式地从到(x)调用?





编辑

编译错误在下面添加 - 我真的只是想了解:是否有一些返回从到(x)无形调用的返回值,我没有满足,还是因为编译器不知道什么将被传入,并且有些类型不适合(例如,我没有添加隐式Date生成器 - 并且case类可能包含任何类型?我希望情况并非如此,并且由于编译器一无所知实际上是传递给类/方法它知道没有问题?)

  GeneratorSpec.scala:44:不能fin d参数gen的隐式值:io.github.robhinds.genotype.Generator [GenericTest.this.evidence $ 1.Repr] 
generate(c)

而我的generate方法只是一个简单的帮助方法,可以得到隐式的 Generator

  def generate [A](a:A)(implicit gen:Generator [A])= gen.generate(a)

解决方案

您的问题来自于 Generic only 将一个case类转换为一个 HList 并将一个密封特征转换为一个 Coproduct (不递归)。

所以如果你有一个通用的 Generic ,你没有关于 HList
Coproduct 的信息,因此,您无法使用产品和副产品规则来查找想要的含蓄。在某些明确的情况下,你可能会遇到同样的问题,所以我将以你为例:



假设你有一个案例类架构

  case class Bottom(value:String)
case class Top(bot:Bottom,count:Int)
$ b

隐含的 Generic [Top] 将有类型MyHList = Bottom :: Int :: HNil 作为输出类型,因此您将要求隐式 Generator [MyHList] 。但是,由于范围中没有隐式的 Generator [Bottom] ,所以您将无法使用 hconsgenerator



在一般情况下,情况更糟。编译器只能为 Generic [A] 的输出类型推断 HList (假设您忘记了 Coproduct ),所以你需要隐含的 Generator [HList] ,这是你不能提供的。



解决方法是给出一个隐含的结构,它具有可以自己生成的泛型:

 隐式def generic2generate [T,L <:HList](隐式泛型:Generic.Aux [T,L],lGen:Generator [L]):Generator [T] = new Generator [T] {
def generate (c:T)= generic.from(lGen.generate(generic.to(c)))
}

编辑



现在您可以按照 Top type:




  • 我们可以有一个 Generator [Top] 使用最后一条规则,如果我们对 L 有一个 Generic.Aux [Top,L] ,并且一个<$ code> Generator [L] 。


  • 唯一的 Generic.Aux [Top, _ 隐含存在的是 Generic.Aux [Top,Bottom :: Int :: HNil] ,所以我们简化为找到 Generator [Top,Bottom :: Int :: HNil]


  • 使用hcons规则三次,我们简化为找到 Generator [Bottom] ,a Generator [Int] 和一个 Generator [HNil]


  • 发生器[Int] 假设)和 Generator [HNil] 是第一条规则,所以我们简化为找到 Generator [Bottom]


  • 唯一可以提供的规则是第三条规则,所以我们必须找到一个 Generator [String :: HNil] ,因为只有 Generic 可用是 Generic.Aux [Bottom,String :: HNil] Generator [String] ,这可以很容易地提供。




这个例子显示了不同的点:$ b​​
$ b
第二,这个解决方案只能用于特定的 通用,它不能被推断为一般(尽管这可能看起来不符合直觉);即使人类的思想能够证明它对每个 Generic 都有效,编译器无法像这样处理它。



I am still trying to get my head around Shapeless (and, to a lesser extent, Scala!) and I have been writing some simple code to generate random instance data for case classes - predominantly based on the guides here: http://enear.github.io/2016/09/27/bits-of-shapeless-2/ (the example covers a JSON Writer implementation)

I have created a Generator[A] trait and created implicit implementations for simple types, and as per the example in the above link, I have also created implicit implementations to handle HList, HNil, Coproduct and CNil:

  implicit def hnilGenerator = new Generator[HNil] {
    override def generate(a: HNil) = HNil
  }

  implicit def hconsGenerator[H, T <: HList](implicit headGen: Generator[H], tailGen: Generator[T]) =
    new Generator[H :: T] {
      override def generate(a: H :: T) = headGen.generate(a.head) :: tailGen.generate(a.tail)
    }

  implicit def cnilGenerator: Generator[CNil] =
    new Generator[CNil] {
      override def generate(a: CNil): CNil = throw new RuntimeException("Invalid candidate configuration")
    }

  implicit def cconsGenerator[H, T <: Coproduct] =
    new Generator[H :+: T] {
      override def generate(a: H :+: T) = throw new RuntimeException("Invalid candidate configuration")
    }

I can now use this code to generate a random instance based on a case class or a sealed trait:

    it("should work with a case class to hlist") {
      case class Test(x: IntGene, y: DoubleGene, z: BooleanGene)
      val c = Generic[Test].to(Test(IntGene(), DoubleGene(), BooleanGene()))
      generate(c)
    }
    it("what happens with sealed traits") {
      sealed trait Shape
      case class Square(width: Int, height: Int) extends Shape
      case class Circle(radius: Int) extends Shape

      val c = Generic[Shape].to(Circle(1))
      generate(c)
    }

Both of the above work no problem, however, if I try to make this a generic (as in parameter types) I get compilation errors not being able to find the necessary implicts:

it("should handle generics") {
  case class GenericTest[A: Generic](x: A) {
    def convert() = {
      val c = Generic[A].to(x)
      generate(c)
    }
  }
}

So from my understanding, because I have used the Generic context bound A, the compiler knows that is going to be available, so c must be some possible return from the call to(x) - Am I missing something in the implementation to handle that return type from the Generic shapeless call? Or have I wildly misunderstood something?

I am hoping this is possible and I have just missed something - is it that the compiler doesn't know what will be passed in (Im assuming not), or is there another possible type that needs to be handled implicitly from that to(x) call?


EDIT

Compile error added below - I'm really just trying to understand: Is it that there is some return case from the to(x) shapeless call that I have not catered for, or is it because the compiler doesn't have any idea what will be passed in and there are some types not catered for (e.g. I haven't added a Date generator implicit - and a case class could potentially have any type included? I was hoping that was not the case, and as the compiler knows nothing is actually being passed to the class/method it knows there are no issues?)

GeneratorSpec.scala:44: could not find implicit value for parameter gen: io.github.robhinds.genotype.Generator[GenericTest.this.evidence$1.Repr]
          generate(c)

And my generate method is just a simple helper method that gets given the implicit Generator :

def generate[A](a: A)(implicit gen: Generator[A]) = gen.generate(a)

解决方案

Your problem comes from the fact that Generic only converts a case class to a HList and a sealed trait to a Coproduct (not recursively).

So if you have a generic Generic, you have no information on what HList or Coproduct you are given, so, you cannot use your product and coproduct rules to find the wanted implicit. In some explicit case, you might have the same problem, so I will give you this as an example:

Let's say you have a case class architecture

case class Bottom(value: String)
case class Top(bot: Bottom, count: Int)

The implicit Generic[Top] will have type MyHList = Bottom :: Int :: HNil as output type, so you'll be asking for an implicit Generator[MyHList]. But since you don't have an implicit Generator[Bottom] in scope, you won't be able to use hconsgenerator.

In the generic case, it's even worse. The compiler can only infer HList for the output type of Generic[A] (and that's assuming you forget about Coproduct), so you need an implicit Generator[HList], which you cannot provide.

The solution is to give an implicit for constructs that have a generic that can itself be generated:

implicit def generic2generate[T, L <: HList](implicit generic: Generic.Aux[T, L], lGen: Generator[L]): Generator[T] = new Generator[T] {
  def generate(c: T) = generic.from(lGen.generate(generic.to(c)))
}

EDIT

You can now follow the implicit resolution for our Top type:

  • We can have a Generator[Top] using last rule, if we have a Generic.Aux[Top, L] for some L, and a Generator[L].

  • The only Generic.Aux[Top, _] that exists implicitly is a Generic.Aux[Top, Bottom :: Int :: HNil], so we are reduced to finding a Generator[Top, Bottom :: Int :: HNil]

  • using the hcons rule three times, we are reduced to finding a Generator[Bottom], a Generator[Int] and a Generator[HNil].

  • Generator[Int] is given (I assume) and Generator[HNil] is the first rule, so we are reduced to finding a Generator[Bottom]

  • the only rule that can provide one, is once again the 3rd rule, so we must find a Generator[String :: HNil], since the only Generic available is a Generic.Aux[Bottom, String :: HNil].

  • using the hcons rule, we are down to finding a Generator[String], which can easily be provided.

This example shows different points:

  • first that it may take a long time when compiling to solve all these implicits (I only gave the main points of the proof, but the compiler has to try all possible branches)

  • second, that this resolution can only be done for a specific Generic, it cannot be inferred generically (although this might seem counter-intuitive); even if the human mind is able to tell that it will work for every Generic, the compiler cannot process it as such.

这篇关于无形不能在通用环境中工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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