Shapeless - Coproduct 中的重复数据删除类型
Shapeless - Deduplicating types in Coproduct
鉴于我有一个 Int :+: Int :+: String :+: CNil
类型,有没有简单的方法可以将它变成 Int :+: String :+: CNil
?
这个方法简单吗?
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]]
这取决于你所说的 "easy" 是什么意思。我很确定没有直接的方法来通过组合在 Shapeless 中现成可用的余积操作来执行此操作,但是编写自己的类型 class 来执行此操作相当简单(至少就目前而言)这些东西去了)。
我将假设您的要求中未指定的几件事:
- 您希望生成的余积中的类型是唯一的(例如,您不只是像示例中那样折叠相邻元素)。
- 在非相邻重复类型的情况下,您希望 last 重复项包含在结果中。
如果这些假设不准确,调整下面的解决方案并不难——核心思想是一样的。
完整的解决方案如下所示:
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
}
这是我们的类型class。它表征了一个副积 C
,对于任何实例,它都有一个由 C
确定的唯一输出类型,它也是一个副积。从 DepFn1
我们得到一个方法 apply
,它接受一个 C
和 returns 一个 Out
;这就是我们将在下面的实例中实施的内容。
在伴随对象中,我们有几行基本上是样板文件——它们不是绝对必要的,但它们支持方便、惯用地使用这种类型 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]]
(还有 returns精炼结果而不是无用的未精炼 Unique[C]
).
接下来我们有我们的基本案例:
implicit val uniqueCNil: Aux[CNil, CNil] = new Unique[CNil] {
type Out = CNil
def apply(c: CNil): CNil = c
}
这相当简单:如果我们得到一个空的余积,我们就知道它的元素已经是唯一的。
接下来我们有几个归纳案例。第一个 uniqueCCons1
涵盖尾部存在余积头部的情况,第二个 uniqueCCons0
涵盖不存在的情况。因为 uniqueCCons1
适用于 uniqueCCons0
涵盖的案例子集,所以我们必须明确区分这两个实例的优先级。我正在使用 subclass 来降低 uniqueCCons0
的优先级,因为我认为这是最简单的方法。
这两个实例的实现可能看起来有点乱,但其实逻辑并没有那么复杂。在这两种情况下,我们都有一个归纳 Unique[R]
实例;不同之处在于,在 1
的情况下,我们首先将头部注入尾部(依靠 Shapeless 的 Inject
类型 class 见证 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@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))
符合我们上面的要求。
鉴于我有一个 Int :+: Int :+: String :+: CNil
类型,有没有简单的方法可以将它变成 Int :+: String :+: CNil
?
这个方法简单吗?
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]]
这取决于你所说的 "easy" 是什么意思。我很确定没有直接的方法来通过组合在 Shapeless 中现成可用的余积操作来执行此操作,但是编写自己的类型 class 来执行此操作相当简单(至少就目前而言)这些东西去了)。
我将假设您的要求中未指定的几件事:
- 您希望生成的余积中的类型是唯一的(例如,您不只是像示例中那样折叠相邻元素)。
- 在非相邻重复类型的情况下,您希望 last 重复项包含在结果中。
如果这些假设不准确,调整下面的解决方案并不难——核心思想是一样的。
完整的解决方案如下所示:
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
}
这是我们的类型class。它表征了一个副积 C
,对于任何实例,它都有一个由 C
确定的唯一输出类型,它也是一个副积。从 DepFn1
我们得到一个方法 apply
,它接受一个 C
和 returns 一个 Out
;这就是我们将在下面的实例中实施的内容。
在伴随对象中,我们有几行基本上是样板文件——它们不是绝对必要的,但它们支持方便、惯用地使用这种类型 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]]
(还有 returns精炼结果而不是无用的未精炼 Unique[C]
).
接下来我们有我们的基本案例:
implicit val uniqueCNil: Aux[CNil, CNil] = new Unique[CNil] {
type Out = CNil
def apply(c: CNil): CNil = c
}
这相当简单:如果我们得到一个空的余积,我们就知道它的元素已经是唯一的。
接下来我们有几个归纳案例。第一个 uniqueCCons1
涵盖尾部存在余积头部的情况,第二个 uniqueCCons0
涵盖不存在的情况。因为 uniqueCCons1
适用于 uniqueCCons0
涵盖的案例子集,所以我们必须明确区分这两个实例的优先级。我正在使用 subclass 来降低 uniqueCCons0
的优先级,因为我认为这是最简单的方法。
这两个实例的实现可能看起来有点乱,但其实逻辑并没有那么复杂。在这两种情况下,我们都有一个归纳 Unique[R]
实例;不同之处在于,在 1
的情况下,我们首先将头部注入尾部(依靠 Shapeless 的 Inject
类型 class 见证 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@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))
符合我们上面的要求。