Это зависит от того, что вы подразумеваете под «легким».Я почти уверен, что не существует простого способа выполнить эту операцию, сочиняя сопутствующие операции, которые доступны в Shapeless, но написать собственный класс типов для этого достаточно просто (по крайней мере, до такой степени).
Я собираюсь предположить пару вещей, которые не указаны в ваших требованиях:
- Вы хотите, чтобы типы в полученном копродукте были уникальными (например, вы 'не просто сворачивать смежные элементы, как в вашем примере).
- В случае несмежных дублированных типов, вы хотите, чтобы дубликат последний был включен в результат.
Было бы несложно настроить приведенное ниже решение, если эти предположения не точны - основная идея была бы такой же.
Полное решение выглядит следующим образом:
import shapeless.{ :+:, CNil, Coproduct, DepFn1, Inl, Inr }
import shapeless.ops.coproduct.Inject
trait Unique[C <: Coproduct] extends DepFn1[C] {
type Out <: Coproduct
}
object Unique extends LowPriorityUnique {
type Aux[C <: Coproduct, Out0 <: Coproduct] = Unique[C] { type Out = Out0 }
def apply[C <: Coproduct](implicit unC: Unique[C]): Aux[C, unC.Out] = unC
implicit val uniqueCNil: Aux[CNil, CNil] = new Unique[CNil] {
type Out = CNil
def apply(c: CNil): CNil = c
}
implicit def uniqueCCons1[L, R <: Coproduct](implicit
inj: Inject[R, L],
unR: Unique[R]
): Aux[L :+: R, unR.Out] = new Unique[L :+: R] {
type Out = unR.Out
def apply(c: L :+: R): unR.Out = unR(
c match {
case Inl(l) => inj(l)
case Inr(r) => r
}
)
}
}
class LowPriorityUnique {
implicit def uniqueCCons0[L, R <: Coproduct](implicit
unR: Unique[R]
): Unique[L :+: R] { type Out = L :+: unR.Out } = new Unique[L :+: R] {
type Out = L :+: unR.Out
def apply(c: L :+: R): L :+: unR.Out = c match {
case Inl(l) => Inl(l)
case Inr(r) => Inr(unR(r))
}
}
}
Мы можем пошагово пройти этот код.
trait Unique[C <: Coproduct] extends DepFn1[C] {
type Out <: Coproduct
}
Это наш класс типов.Он характеризует побочный продукт C
, и для любого экземпляра имеет уникальный тип вывода, определяемый C
, который также является побочным продуктом.Из DepFn1
мы получаем метод apply
, который принимает C
и возвращает Out
;это то, что мы будем реализовывать в следующих примерах.
В объекте-компаньоне у нас есть пара строк, которые в основном являются шаблонными - они не являются строго необходимыми, но они поддерживают удобное идиоматическое использование этогоclass class:
type Aux[C <: Coproduct, Out0 <: Coproduct] = Unique[C] { type Out = Out0 }
def apply[C <: Coproduct](implicit unC: Unique[C]): Aux[C, unC.Out] = unC
Первая строка позволяет нам избежать повсеместного написания уточнений типов (Foo[X] { type Bar = Bar0 }
), а вторая позволяет писать Unique[C]
вместо implicitly[Unique[C]]
(а также возвращает уточненный результат вместобесполезного нерафинированного Unique[C]
).
Далее у нас есть базовый случай:
implicit val uniqueCNil: Aux[CNil, CNil] = new Unique[CNil] {
type Out = CNil
def apply(c: CNil): CNil = c
}
Это довольно просто: если мы получаем пустой копроизведение, мы знаем, что его элементы уже уникальны.
Далее у нас есть пара индуктивных случаев.Первый, uniqueCCons1
, охватывает случай, когда головка копродукта существует в хвосте, а второй, uniqueCCons0
, охватывает случай, когда его нет.Поскольку uniqueCCons1
применяется к подмножеству случаев uniqueCCons0
, мы должны явно расставить приоритеты для этих двух экземпляров.Я использую подкласс, чтобы дать uniqueCCons0
более низкий приоритет, так как я думаю, что это самый простой подход.
Реализации этих двух экземпляров могут выглядеть немного грязными, но логика на самом деле не так сложна,В обоих случаях мы имеем индуктивный Unique[R]
экземпляр;разница в том, что в случае 1
мы сначала вводим голову в хвост (полагаясь на класс * Shapeless типа Inject
, чтобы засвидетельствовать, что L
встречается в R
), а затем применяем unR
, где в 0
В случае, если мы применяем его только к хвосту и оставляем голову без изменений.
Это работает так:
scala> type C = Int :+: String :+: CNil
defined type alias C
scala> Unique[C]
res0: Unique[Int :+: String :+: shapeless.CNil]{type Out = Int :+: String :+: shapeless.CNil} = LowPriorityUnique$$anon$3@2ef6f000
scala> Unique[C].apply(Inl(1))
res1: Int :+: String :+: shapeless.CNil = Inl(1)
scala> type C2 = Int :+: String :+: Int :+: CNil
defined type alias C2
scala> Unique[C2].apply(Inr(Inr(Inl(1))))
res2: String :+: Int :+: shapeless.CNil = Inr(Inl(1))
scala> Unique[C2].apply(Inl(1))
res3: String :+: Int :+: shapeless.CNil = Inr(Inl(1))
Что соответствует нашим требованиям выше.