【问题标题】:Shapeless - Deduplicating types in CoproductShapeless - Coproduct 中的重复数据删除类型
【发布时间】:2019-07-25 14:12:54
【问题描述】:

鉴于我的类型为Int :+: Int :+: String :+: CNil,有没有一种简单的方法可以将其转换为Int :+: String :+: CNil

【问题讨论】:

    标签: scala generics shapeless


    【解决方案1】:

    这是一个简单的方法吗?

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

    【讨论】:

    • 你比我早了几分钟,但我不认为只用代码回答这样的复杂问题不是一个好主意,所以我要离开我的了。
    【解决方案2】:

    这取决于您所说的“简单”是什么意思。我很确定没有直接的方法可以通过组合Shapeless中现成的副产品操作来执行此操作,但是编写自己的类型类来执行此操作相当简单(至少就这些事情而言) .

    我将假设您的要求中未指定的几件事:

    1. 您希望生成的副产品中的类型是唯一的(例如,您不只是像示例中那样折叠相邻元素)。
    2. 在不相邻的重复类型的情况下,您希望 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
    }
    

    这是我们的类型类。它表征了一个副产品C,并且在任何情况下,它都有一个由C 确定的唯一输出类型,它也是一个副产品。从DepFn1 我们得到一个方法apply,它接受C 并返回Out;这就是我们将在下面的实例中实现的。

    在伴生对象中,我们有几行基本上是样板文件——它们不是绝对必要的,但它们支持这种类型类的方便、惯用的用法:

    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,其中@ 987654346@ 情况下,我们只将其应用于尾部,而头部保持不变。

    它是这样工作的:

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

    符合我们上面的要求。

    【讨论】:

    • 啊,所以Inject 是我需要的类型类。谢谢特拉维斯!如果您可以添加一些关于Inject 证明我们的头部类型L 存在于R 中的注释,那就太好了! (如果我的理解是正确的)
    • @JacobWang 好主意——我刚刚添加了一条注释。
    猜你喜欢
    • 2023-02-24
    • 2018-10-25
    • 1970-01-01
    • 1970-01-01
    • 2018-09-19
    • 2023-03-04
    • 2020-04-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多