Бесформенный: в чем разница между этими двумя подходами к созданию экземпляров?
Может ли кто-нибудь объяснить мне, в чем разница между этими двумя подходами к получению экземпляров класса типов (особенно для варианта [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
экземпляры для этого? Я прав, если скажу, что первый подход дает нам немного больше гибкости?
2 ответа
Я предполагаю, что под "работал правильно" вы имеете в виду "скомпилирован" или "работал для какого-то простого варианта использования".
Оба ваших примера имеют дело с общими типами продуктов, но не с типами общих сумм, поэтому нет риска, что, например, 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.?
Во второй версии вы можете получить MyTrait[Option[A]]
если вы можете получить A
, и можно вывести для любого A
который является примитивным / option / product - поэтому Option[Option[String]]
, Option[String]
а также Option[SomeCaseClass]
должно все работать. Он также должен работать, если этоSomeCaseClass
содержит поля, которые Option
s или другие классы case, которые Option
s и т. д.
Версия 1. немного отличается:
- сначала вы ищете примитивы
- затем вы пытаетесь получить продукт (например,
Option
не обрабатывались бы здесь) - тогда вы делаете что-то странное:
genericOption
предполагает, что вы создалиOption[Repr]
, а затем я думаю, сопоставьте его, используяRepr
- чтобы построить это
Repr
Ты взялOption[HNil]
и добавить типы внутриOption
с помощьюproductOption
, который сломался бы, если бы кто-то использовалOption
как поле - поэтому вы "исправляете" это, добавляя
Option
в особом случаеproduct2
Я думаю, вы тестировали это только с классами case, потому что первая версия не будет работать для:
Option
для примитивов (Option[String]
,Option[Int]
или что вы определили как примитив)- вложенные параметры (
Option[Option[String]]
) - параметры для настраиваемых типов, которые не являются классами case, но имеют экземпляры, определенные вручную:
class MyCustomType object MyCustomType { implicit val myTrait: MyTrait[MyCustomType] } implicitly[Option[MyCustomType]]
По этой причине любое решение с implicit def forOption[A](implicit instance: MyTrait[A]): MyTrait[Option[A]]
проще и пуленепробиваемый.
В зависимости от того, что вы помещаете непосредственно в сопутствующий баннер, могут быть нужны или не нужны низкоприоритетные имплициты:
- если вы определили сопродукты, тогда ручная поддержка, например,
Option
,List
,Either
может конфликтовать с бесформенными производными - если вы вручную реализовали
MyTrait
неявно для некоторого типа в его сопутствующем объекте, тогда он будет иметь тот же приоритет, что и имплициты непосредственно вMyTrait
- поэтому, если бы его можно было получить с помощью shapeless, у вас могли бы возникнуть конфликты
По этой причине имеет смысл добавить бесформенные имплициты в LowPriorityImplicits
но примитивы и ручные кодеки для List, Option, Either и т. д. прямо в сопутствующем файле. То есть, если вы не определили, например,Option[String]
имплицит прямо в сопутствующем элементе, который может конфликтовать с "Option[A]
с неявным для A
".
Поскольку я не знаю вашего точного варианта использования, я не могу сказать наверняка, но я, вероятно, выбрал бы секундный подход или, скорее всего, тот, который я реализовал в приведенном выше фрагменте.
На самом деле сложно сказать без правых частей и реальных реализаций.
Из предоставленной вами информации не следует, что два класса типов ведут себя одинаково.
Например, в первом подходе вы рассматриваете некоторые частные случаи, поэтому теоретически возможно, что вы по-другому переопределите какое-то общее поведение в частном случае.
Кстати, Option[A]
является совместным продуктом Some[A]
а также None.type
(List[A]
является совместным продуктом scala.::[A]
а также Nil.type
), и иногда легче получить класс типа для копродуктов, чем для Option[A]
(или List[A]
).