【问题标题】:Shapeless: what the difference between these two approaches of instance derivation?Shapeless:这两种实例派生方法有什么区别?
【发布时间】:2020-12-08 22:25:30
【问题描述】:

谁能解释一下这两种类型类实例派生方法之间的区别(特别是对于 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 实例吗? 如果我会说第一种方法给了我们更多的灵活性,我说得对吗?

【问题讨论】:

    标签: scala typeclass shapeless deriving generic-derivation


    【解决方案1】:

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

    您的两个示例都处理通用产品类型,但不涉及通用总和类型,因此没有风险,例如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,即原始/选项/产品 - 所以Option[Option[String]]Option[String]Option[SomeCaseClass]应该都可以。如果此SomeCaseClass 包含Options 的字段或Options 等其他案例类,它也应该工作。

    版本 1. 略有不同:

    • 起初您正在寻找原语
    • 然后您尝试为产品派生(例如,Option 不会在此处处理)
    • 然后你做了一些奇怪的事情:
      • genericOption 假设您创建了一个Option[Repr],然后我猜想使用Repr 映射它
      • 为了构建 Repr,您需要使用 Option[HNil] 并使用 productOptionOption 中添加类型,如果有人使用 Option 作为字段,则会中断
      • 所以你通过在特殊情况下添加Option 来“修复”它product2

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

    • 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]] 的任何解决方案都更简单、更安全。

    根据您直接放入伴随的低优先级隐式中的内容,可能需要也可能不需要:

    • 如果您定义了副产品,则手动支持例如OptionListEither 可能与无形派生的冲突
    • 如果您在其伴生对象中为某些类型手动实现 MyTrait 隐式,那么它将具有与直接在 MyTrait 中的隐式相同的优先级 - 所以如果它可以使用 shapeless 派生,您可能会遇到冲突

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

    由于我不知道您的确切用例,我无法确定,但我可能会采用秒方法,或者很可能采用我在上面的 sn-p 中实现的方法。

    【讨论】:

    • 谢谢!关于第一个版本 - 我第一次在 doobie 项目中看到这个,当时我试图了解它如何与 shapeless (github.com/tpolecat/doobie/blob/master/modules/core/src/main/…) 一起工作。之后我尝试实现类似的东西,但对这种方法特别有疑问,因为我在许多 shapeless 的示例/教程中从未见过这种方法
    • 我认为在 Doobie 的情况下这是需要的。在那里,“Write[Option[A]] from Put[A]”的行为可能适用于原语,而在 Option[H :: T] 中附加值可用于在添加 Option[H] 时使用不同的行为(其中处理 null 不需要将整个最终结果归零)或非Option H (例如,一个字段中的null 可能导致整个Repr/A 中的一个都没有)。所以我们在这里所期望的行为是不同的。
    • 所以三层是有意义的:如果你有Put[A],把它提升到Write[Option[A]](同伴),如果你有一个非可选的产品要处理,使用派生@987654373任何字段上的@都会导致错误(LowerPriority),如果有Option[A],其中A是产品类型,那么可以处理Option字段上的null,但null上的非可选字段将是整个 A (EvenLowerPrioroty) 的 None。将 EvenLower 设置为与 Lower 相同的级别可能会导致 Option 派生冲突。
    • 谢谢!这对于理解至关重要 ?
    • 没问题,虽然只是胡乱猜测,没有任何调查,所以不要把它当作一个确定的答案,更像是探索的建议。
    【解决方案2】:

    实际上,如果没有右手边和实际实现,就很难说。

    从您提供的信息来看,这两个类型类的行为并不相同。

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

    顺便说一下,Option[A]Some[A]None.type 的副产品(List[A]scala.::[A]Nil.type 的副产品),有时为副产品派生类型类比为Option[A](或List[A])。

    【讨论】:

    • 谢谢!似乎在我的情况下它会产生相同的结果
    猜你喜欢
    • 2013-09-08
    • 2019-03-31
    • 1970-01-01
    • 1970-01-01
    • 2020-02-14
    • 2011-01-10
    • 2016-07-01
    • 2013-08-08
    相关资源
    最近更新 更多