【问题标题】:Shapeless not working in generic context无形不在通用上下文中工作
【发布时间】:2017-11-05 14:25:01
【问题描述】:

我仍在尝试了解 Shapeless(在较小程度上还有 Scala!),并且我一直在编写一些简单的代码来为案例类生成随机实例数据 - 主要基于此处的指南:@987654321 @(该示例涵盖了 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 上下文绑定A,编译器知道它将可用,所以c 必须是调用to(x) 的一些可能返回 - 我是在实现中缺少一些东西来处理来自Generic shapeless 调用的返回类型?还是我误会了什么?

我希望这是可能的,我只是错过了一些东西 - 是编译器不知道将传入什么(我假设不知道),还是有另一种可能的类型需要从中隐式处理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)

【问题讨论】:

  • 为什么需要一个值来生成另一个值?关于您的问题,您是否看过 shapeless.TypeClassCompanion,它是用来处理 ADT 的类型类的?
  • 粘贴编译错误可能会有用。
  • 可能在代码中的某处键入推理中断,您可能希望对 Scalac 有所帮助。
  • 还有,generate()的类型签名是什么?
  • @CyrilleCorpet 没有真正的理由 - 我正在考虑使用对象传递其他配置的想法(为生成的值提供约束,最大/分钟等) - 但就像我说的那样为了娱乐 :)。谢谢 - 将看看 TypeClassCompanion 代码

标签: scala generics shapeless


【解决方案1】:

您的问题来自以下事实:Generic only 将案例类转换为 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

在一般情况下,情况更糟。编译器只能为Generic[A] 的输出类型推断HList(假设您忘记了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 类型的隐式解析:

  • 如果我们有一些LGeneric.Aux[Top, L]Generator[L],我们可以使用最后一条规则获得Generator[Top]

  • 唯一隐式存在的Generic.Aux[Top, _]Generic.Aux[Top, Bottom :: Int :: HNil],所以我们只能找到Generator[Top, Bottom :: Int :: HNil]

  • 使用 hcons 规则 3 次,我们只能找到 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,编译器也无法处理它。

【讨论】:

  • 太棒了,谢谢,我会看看添加这个 - 我确实短暂地考虑过我可能必须处理这个案例(tbh,原始链接暗示它,但我认为我弄错了我的问题的翻译)。
【解决方案2】:

我认为您缺少 Generator 类型绑定。

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

【讨论】:

  • 我认为A 不需要有Generator 类型类(因为它应该从Generic 推断出来),而是它的通用表示:case class GenericTest[A, Repr: Generator](x: A)(implicit val gen: Generic.Aux[A, Repr])
猜你喜欢
  • 1970-01-01
  • 2014-07-05
  • 2014-11-10
  • 2013-05-07
  • 2019-05-03
  • 2022-06-29
  • 2023-01-28
  • 2016-08-16
  • 2014-08-13
相关资源
最近更新 更多