Shapeless:这两种实例派生方法有什么区别?

Shapeless: what the difference between these two approaches of instance derivation?

谁能解释一下这两种类型类实例派生方法(特别是 Option[A])的区别?

1.

trait MyTrait[A] {...}

object MyTrait extends LowPriority {
 // instances for primitives
}

trait LowPriority extends LowestPriority {
 final implicit def generic[A, H <: HList](
    implicit gen: Generic.Aux[A, H],
    h: Lazy[MyTrait[H]]
  ): MyTrait[A] = ???

  final implicit val hnil: MyTrait[HNil] = ???

  final implicit def product[H, T <: HList](
    implicit h: Lazy[MyTrait[H]],
    t: Lazy[MyTrait[T]]
  ): MyTrait[H :: T] = ???
}

// special instances for Options
trait LowestPriority {
  implicit def genericOption[A, Repr <: HList](
    implicit gen: Generic.Aux[A, Repr],
    hEncoder: Lazy[MyTrait[Option[Repr]]]
  ): MyTrait[Option[A]] = ???

  implicit val hnilOption: MyTrait[Option[HNil]] = ???

  implicit def productOption1[H, T <: HList](
    implicit
    head: Lazy[MyTrait[Option[H]]],
    tail: Lazy[MyTrait[Option[T]]],
    notOption: H <:!< Option[Z] forSome { type Z }
  ): MyTrait[Option[H :: T]] = ???

  implicit def product2[H, T <: HList](
    implicit
    head: Lazy[MyTrait[Option[H]]],
    tail: Lazy[MyTrait[Option[T]]
  ): MyTrait[Option[Option[H] :: T]] = ???
}
trait MyTrait[A] {...}

object MyTrait extends LowPriority {
 // instances for primitives
}

trait LowPriority {
// deriving instances for options from existing non-option instances
 final implicit def forOption[A](implicit instance: MyTrait[A]): MyTrait[Option[A]] = ??? // <<<----

 final implicit def generic[A, H <: HList](
    implicit gen: Generic.Aux[A, H],
    h: Lazy[MyTrait[H]]
  ): MyTrait[A] = ???

  final implicit val hnil: MyTrait[HNil] = ???

  final implicit def product[H, T <: HList](
    implicit h: Lazy[MyTrait[H]],
    t: Lazy[MyTrait[T]]
  ): MyTrait[H :: T] = ???
}

我都试过了,它们都工作正常,但我不确定它们是否会在所有情况下产生相同的结果(也许我错过了什么)。

我们真的需要 LowestPriority 个实例吗? 如果我说第一种方法给了我们更多的灵活性,我是对的吗?

实际上,没有右侧和实​​际实现很难说。

根据您提供的信息,这两种类型 class 的行为并不相同。

例如,在第一种方法中,您考虑了一些特殊情况,因此理论上您可以以不同的方式重新定义特殊情况下的某些一般行为。

顺便说一下,Option[A]Some[A]None.type 的余积(List[A]scala.::[A]Nil.type 的余积) 并且有时为联积推导类型 class 比 Option[A](或 List[A])更容易。

我假设“工作正常”是指“已编译”或“适用于一些简单的用例”。

您的两个示例都涉及通用产品类型,但不涉及通用总和类型,因此不存在例如Option[A] 可以使用 Some[A] :+: None :+: CNil 派生,这会造成一些歧义。所以(据我所知)你可以像这样写第二个版本:

trait MyTrait[A] {...}

object MyTrait extends LowPriority {
 // instances for primitives

// deriving instances for options from existing non-option instances
 final implicit def forOption[A](implicit instance: MyTrait[A]): MyTrait[Option[A]] = ??? 
}

trait LowPriority {
// <<<----
 final implicit def hcons[A, H <: HList](
    implicit gen: Generic.Aux[A, H],
    h: Lazy[MyTrait[H]]
  ): MyTrait[A] = ???

  final implicit val hnil: MyTrait[HNil] = ???

  final implicit def product[H, T <: HList](
    implicit h: Lazy[MyTrait[H]],
    t: Lazy[MyTrait[T]]
  ): MyTrait[H :: T] = ???
}

并且它会正确地推导出东西。

但是 1. 和 2. 有何不同?

在第二个版本中,如果可以推导 A,则可以推导 MyTrait[Option[A]],并且可以推导任何 A,即 primitive/option/product - 所以 Option[Option[String]]Option[String]Option[SomeCaseClass] 应该都可以。如果此 SomeCaseClass 包含 Options 的字段,或者其他 类 的 Options 字段,它也应该有效,等等

版本 1. 略有不同:

  • 一开始你在寻找基元
  • 然后您尝试推导产品(例如 Option 不会在此处处理)
  • 然后你做了一些奇怪的事情:
    • genericOption 假设您创建了一个 Option[Repr],然后我猜想使用 Repr
    • 映射它
    • 为了构建 Repr,您采用 Option[HNil] 并使用 productOptionOption 中添加类型,如果有人将 Option 用作一个字段
    • 所以你通过在特殊情况下 product2
    • 前面加上 Option 来“修复”

我想,您只针对案例 类 测试了它,因为第一个版本不适用于:

  • Option 用于原语(Option[String]Option[Int] 或您定义为原语的任何内容)
  • 嵌套选项(Option[Option[String]]
  • 自定义类型的选项,不区分大小写 类 但具有手动定义的实例:
    class MyCustomType
    object MyCustomType {
      implicit val myTrait: MyTrait[MyCustomType]
    }
    implicitly[Option[MyCustomType]]
    

因此,使用 implicit def forOption[A](implicit instance: MyTrait[A]): MyTrait[Option[A]] 的任何解决方案都更简单、更可靠。

取决于您直接放入伴侣 low-priority 的内容,可能需要也可能不需要:

  • 如果您定义了副产品,则手动支持例如OptionListEither 可能与无形派生的冲突
  • 如果您手动实现 MyTrait implicit for some type in its companion object 那么它将具有与直接在 MyTrait 中隐式相同的优先级 - 所以如果它可以使用 shapeless 派生,你可以有冲突

出于这个原因,在 LowPriorityImplicits 中放置无形隐式是有意义的,但将 List、Option、Either 等的原语和手动编解码器直接放在伴随中。也就是说,除非你定义了一些,例如Option[String] 直接隐含在 companion 中,这可能与“Option[A]A 隐含”冲突。

由于我不知道您的确切用例,所以我无法确定,但我可能会采用秒方法,或者最有可能采用我在上面的代码片段中实现的方法。