Shapeless - дедупликация типов в копроизведении - PullRequest
0 голосов
/ 04 марта 2019

Учитывая, что у меня есть тип Int :+: Int :+: String :+: CNil, есть ли простой способ превратить его в Int :+: String :+: CNil?

Ответы [ 2 ]

0 голосов
/ 04 марта 2019

Это зависит от того, что вы подразумеваете под «легким».Я почти уверен, что не существует простого способа выполнить эту операцию, сочиняя сопутствующие операции, которые доступны в Shapeless, но написать собственный класс типов для этого достаточно просто (по крайней мере, до такой степени).

Я собираюсь предположить пару вещей, которые не указаны в ваших требованиях:

  1. Вы хотите, чтобы типы в полученном копродукте были уникальными (например, вы 'не просто сворачивать смежные элементы, как в вашем примере).
  2. В случае несмежных дублированных типов, вы хотите, чтобы дубликат последний был включен в результат.

Было бы несложно настроить приведенное ниже решение, если эти предположения не точны - основная идея была бы такой же.

Полное решение выглядит следующим образом:

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))

Что соответствует нашим требованиям выше.

0 голосов
/ 04 марта 2019

Это простой способ?

  import shapeless.{:+:, =:!=, CNil, Coproduct, Inl, Inr, unexpected}

  trait Deduplicate[C <: Coproduct] {
    type Out <: Coproduct
    def apply(c: C): Out
  }
  object Deduplicate {
    type Aux[C <: Coproduct, Out0 <: Coproduct] = Deduplicate[C] { type Out = Out0 }
    def instance[C <: Coproduct, Out0 <: Coproduct](f: C => Out0): Aux[C, Out0] = new Deduplicate[C] {
      override type Out = Out0
      override def apply(c: C): Out = f(c)
    }

    implicit def zero: Aux[CNil, CNil] = instance(_ => unexpected)
    implicit def one[H]: Aux[H :+: CNil, H :+: CNil] = instance(identity)
    implicit def duplicates[H, T <: Coproduct](implicit
      dedup: Deduplicate[H :+: T]): Aux[H :+: H :+: T, dedup.Out] = instance {
      case Inl(h) => dedup(Inl(h))
      case Inr(c) => dedup(c)
    }
    implicit def noDuplicates[H, H1, T <: Coproduct](implicit
      dedup: Deduplicate[H1 :+: T],
      ev1: H =:!= H1): Aux[H :+: H1 :+: T, H :+: dedup.Out] = instance {
      case Inl(h) => Inl(h)
      case Inr(c) => Inr(dedup(c))
    }
  }
  implicit class DeduplicateOps[C <: Coproduct](c: C) {
    def deduplicate(implicit dedup: Deduplicate[C]): dedup.Out = dedup(c)
  }

  implicitly[Deduplicate.Aux[String :+: Int :+: Int :+: String :+: String :+: CNil,
    String :+: Int :+: String :+: CNil]]
...