Бесформенный: в чем разница между этими двумя подходами к созданию экземпляров?

Может ли кто-нибудь объяснить мне, в чем разница между этими двумя подходами к получению экземпляров класса типов (особенно для варианта [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 содержит поля, которые Options или другие классы case, которые Options и т. д.

Версия 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]).

Другие вопросы по тегам