我仍然试图了解 Shapeless(以及在较小程度上,Scala!)并且我一直在编写一些简单的代码来为案例类生成随机实例数据 - 主要基于这里的指南:http://enear.github.io/2016/09/27/bits-of-shapeless-2/(示例涵盖JSON Writer 实现)

我创建了一个 Generator[A] trait 并为简单类型创建了隐式实现,根据上面链接中的示例,我还创建了隐式实现来处理 HList、HNil、Coproduct 和 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")
    }

我现在可以使用此代码基于案例类或密封特征生​​成随机实例:
    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)
    }

以上两个都没有问题,但是,如果我尝试将其设为泛型(如在参数类型中),则会出现编译错误,无法找到必要的含义:
it("should handle generics") {
  case class GenericTest[A: Generic](x: A) {
    def convert() = {
      val c = Generic[A].to(x)
      generate(c)
    }
  }
}

所以根据我的理解,因为我使用了 Generic 上下文绑定(bind) A ,编译器知道它将可用,所以 c 必须是调用 to(x) 的一些可能的返回 - 我在实现中遗漏了什么来处理来自Generic 无形调用?还是我误解了什么?

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

编辑

下面添加了编译错误 - 我真的只是想了解:是否有一些我没有满足的 to(x) shapeless 调用的返回案例,还是因为编译器不知道将传递什么并且有一些类型没有得到满足(例如,我没有添加一个隐式的日期生成器 - 并且一个案例类可能包含任何类型?我希望不是这种情况,因为编译器知道实际上什么都没有传递给它知道没有问题的类/方法?)
GeneratorSpec.scala:44: could not find implicit value for parameter 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 只有 将案例类转换为 HList 并将密封特征转换为 Coproduct (不是递归的)。

因此,如果您有一个通用的 Generic ,则您没有关于所提供的 HListCoproduct 的信息,因此,您不能使用您的乘积和联乘规则来找到想要的隐式。在某些明确的情况下,您可能会遇到同样的问题,因此我将以此为例:

假设您有一个案例类架构

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

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

在一般情况下,情况更糟。编译器只能为 HList 的输出类型推断 Generic[A] (假设您忘记了 Coproduct ),因此您需要一个隐式 Generator[HList] ,而您无法提供。

解决方案是为具有本身可以生成的泛型的构造提供一个隐式:
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)))
}

编辑

您现在可以遵循我们的 Top 类型的隐式解析:
  • 如果我们有一些 Generator[Top]Generic.Aux[Top, L] 和一个 L ,我们可以使用最后一条规则获得 Generator[L]
  • 唯一隐含存在的 Generic.Aux[Top, _]Generic.Aux[Top, Bottom :: Int :: HNil] ,因此我们简化为查找 Generator[Top, Bottom :: Int :: HNil]
  • 使用 hcons 规则三次,我们被简化为找到一个 Generator[Bottom] 、一个 Generator[Int] 和一个 Generator[HNil]
  • Generator[Int] 是给定的(我假设)并且 Generator[HNil] 是第一条规则,所以我们被简化为找到一个 Generator[Bottom]
  • 唯一可以提供的规则,再次是第三条规则,所以我们必须找到一个 Generator[String :: HNil] ,因为唯一可用的 GenericGeneric.Aux[Bottom, String :: HNil]
  • 使用 hcons 规则,我们要找到一个 Generator[String] ,它可以很容易地提供。

  • 这个例子显示了不同的点:
  • 首先,编译解决所有这些隐式可能需要很长时间(我只给出了证明的要点,但编译器必须尝试所有可能的分支)
  • 其次,这个解析只能针对 特定的 Generic 完成,它不能被推断为 一般的 (尽管这可能看起来违反直觉);即使人类的大脑能够判断它对 的每个 Generic 都有效,编译器也不能这样处理它。
  • 关于scala - 无形在通用上下文中不起作用,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44358332/

    10-11 17:37