[英]Shapeless: what the difference between these two approaches of instance derivation?
Can someone explain me what the difference between these two approaches for typeclass instance derivation (specifically for Option[A])?有人可以向我解释这两种类型类实例派生方法(特别是选项 [A])之间的区别吗?
1. 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] = ???
}
I tried both and they worked correctly, but i'm not sure that they will produce the same results for all cases (maybe i've missed something).我都试过了,它们都工作正常,但我不确定它们是否会在所有情况下产生相同的结果(也许我错过了一些东西)。
Do we really need LowestPriority
instances for this?为此,我们真的需要LowestPriority
实例吗? Am i right if i would say that the first approach gives us just a little bit more flexibility?如果我会说第一种方法给了我们更多的灵活性,我是对的吗?
Actually it's hard to say without right hand sides and actual implementations.实际上,如果没有右侧和实际实现,很难说。
From information you provided it doesn't follow that the two type classes behave equivalently.从您提供的信息来看,这两个类型类的行为并不相同。
For example in the 1st approach you consider some special cases, so theoretically it's possible that you redefine some general behavior in special case differently.例如,在第一种方法中,您考虑了一些特殊情况,因此从理论上讲,您可以在特殊情况下以不同的方式重新定义一些一般行为。
By the way, Option[A]
is a coproduct of Some[A]
and None.type
( List[A]
is a coproduct of scala.::[A]
and Nil.type
) and sometimes it's easier to derive a type class for coproducts than for Option[A]
(or List[A]
).顺便说一句, Option[A]
是Some[A]
和None.type
的副产品( List[A]
是scala.::[A]
和Nil.type
的副产品),有时更容易导出类型 class对于副产品而不是Option[A]
(或List[A]
)。
I assuming that by "worked correctly" you mean "compiled" or "worked for some simple use case".我假设“工作正常”是指“已编译”或“适用于一些简单的用例”。
Both of your examples deal with generic product types, but not with generic sum types, so there is no risk that eg Option[A]
could get derived using Some[A]:+: None:+: CNil
, which would enforce some ambiguity.您的两个示例都处理通用产品类型,但不处理通用总和类型,因此不存在例如Option[A]
可以使用Some[A]:+: None:+: CNil
,这会造成一些歧义. So (as far as I can tell) you could write the second version like:所以(据我所知)你可以像这样写第二个版本:
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] = ???
}
and it would derive things correctly.它会正确地推导事物。
But how 1. and 2. differs?但是 1. 和 2. 有何不同?
In second version you can derive MyTrait[Option[A]]
if you can derive for A
, and you can derive for any A
which is primitive/option/product - so Option[Option[String]]
, Option[String]
and Option[SomeCaseClass]
should all work.在第二个版本中,如果可以派生A
,则可以派生MyTrait[Option[A]]
,并且可以派生任何原始/选项/产品A
- 所以Option[Option[String]]
, Option[String]
和Option[SomeCaseClass]
应该都可以。 It should also work if this SomeCaseClass
contains fields which are Option
s, or other case classes which are Option
s, etc.如果此SomeCaseClass
包含Option
s 的字段或其他Option
s 的案例类等,它也应该有效。
Version 1. is slightly different:版本 1. 略有不同:
Option
would not be handled here)然后您尝试推导产品(例如,此处不会处理Option
)genericOption
assumes that you created a Option[Repr]
, and then I guess map it using Repr
genericOption
假设你创建了一个Option[Repr]
,然后我猜 map 它使用Repr
Repr
you take Option[HNil]
and prepend types inside Option
using productOption
, which would break if someone used Option
as a field为了构建Repr
,您使用Option[HNil]
并使用productOption
在Option
中添加类型,如果有人将Option
用作字段,这将会中断Option
in a special case product2
所以你通过在特例product2
中添加一个Option
来“修复”这个问题I guess, you tested that only against case classes, because the first version would not work for:我想,您只针对案例类进行了测试,因为第一个版本不适用于:
Option
for primitives ( Option[String]
, Option[Int]
or whatever you defined as primitive)基元的选项( Option
Option[String]
, Option[Int]
或您定义为基元的任何内容)Option[Option[String]]
)嵌套选项 ( Option[Option[String]]
) class MyCustomType object MyCustomType { implicit val myTrait: MyTrait[MyCustomType] } implicitly[Option[MyCustomType]]
For that reason any solution with implicit def forOption[A](implicit instance: MyTrait[A]): MyTrait[Option[A]]
is simpler and more bulletproof.出于这个原因,任何带有implicit def forOption[A](implicit instance: MyTrait[A]): MyTrait[Option[A]]
的解决方案都更简单、更可靠。
Depending on what you put directly into companion low-priority implicits might be or might not be needed:根据您直接放入伴随低优先级隐式的内容,可能需要也可能不需要:
Option
, List
, Either
could conflict with shapeless derived ones如果您定义了副产品,那么手动支持例如Option
、 List
、 Either
可能会与无形派生的冲突MyTrait
implicit for some type in its companion object then it would have the same priority as implicits directly in MyTrait
- so if it could be derived using shapeless you could have conflicts如果您在其同伴 object 中手动为某些类型隐式实现MyTrait
,那么它将与直接在MyTrait
中隐式具有相同的优先级 - 因此,如果它可以使用 shapeless 派生,您可能会发生冲突For that reason it makes sense to put shapeless implicits in LowPriorityImplicits
but primitives, and manual codecs for List, Option, Either, etc directly in companion.出于这个原因,在LowPriorityImplicits
中放置无形隐式是有意义的,但将 List、Option、Either 等的原语和手动编解码器直接放在 companion 中是有意义的。 That is, unless you defined some eg Option[String]
implicits directly in companion which could clash with " Option[A]
with implicit for A
".也就是说,除非您在同伴中直接定义了一些例如Option[String]
隐含,这可能与“ Option[A]
with implicit for A
”发生冲突。
Since I don't know your exact use case I cannot tell for sure, but I would probably go with the seconds approach, or most likely with the one I implemented in the snippet above.由于我不知道您的确切用例,因此我无法确定,但我可能会使用秒方法 go,或者最有可能使用我在上面的代码片段中实现的方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.