简体   繁体   English

将无形HList转换为较小的HList

[英]Transform shapeless HList into a smaller HList

I have a shapeless HList that has the following structure: 我有一个无形的HList,具有以下结构:

type ABCAB = List[A] :: List[B] :: List[C] :: List[A] :: List[B] :: HNil
val abcab: ABCAB = List[A]() :: List(B) :: List[C]() :: List(A) :: List(B) :: HNil

that I would like to transform into a simpler type where lists of same type are appended, left to right: 我想转换成一个更简单的类型,其中从左到右追加相同类型的列表:

type ABC = List[A] :: List[B] :: List[C] :: HNil        
val abc: ABC = abcab.magic                       // does magic exist in shapeless?
abc == List(A) :: List(B,B) :: List[C]() :: HNil // true

Is there some built-in functionality in shapeless v1.2.4 for this? 为什么在无形v1.2.4中有一些内置功能吗?

While the approach taken in the other answer (introducing a new type class) will work, it's possible to solve this problem using more general machinery—and in a way that's not that different from the way you'd solve similar problems at the value level. 虽然在另一个答案(引入一个新的类型)中采用的方法将起作用,但是可以使用更通用的机器来解决这个问题 - 并且与在价值水平上解决类似问题的方式没有什么不同。

We'll use a left fold. 我们将使用左折。 Writing the combining function is a little tricky, since we have two cases (we've already seen an element with the same type as the current one, or we haven't), and we have to use an implicit prioritization trick to avoid ambiguous implicit values: 编写组合函数有点棘手,因为我们有两种情况(我们已经看到一个与当前元素具有相同类型的元素,或者我们没有),我们必须使用隐式优先级技巧来避免模糊隐含值:

import shapeless._

trait LowPriorityCombine extends Poly2 {
  implicit def notAlreadySeen[L <: HList, A](implicit
    p: Prepend[L, List[A] :: HNil]
  ) = at[L, List[A]](_ :+ _)
}

object combine extends LowPriorityCombine {
  implicit def alreadySeen[L <: HList, A](implicit
    s: Selector[L, List[A]],
    r: Replacer[L, List[A], List[A]]
  ) = at[L, List[A]] {
    case (acc, as) => acc.updatedElem[List[A]](acc.select[List[A]] ++ as)
  }
}

But then we're essentially done: 但后来我们基本上完成了:

def magic[L <: HList](l: L)(implicit f: LeftFolder[L, HNil.type, combine.type]) =
  l.foldLeft(HNil)(combine)

We can show that it works: 我们可以证明它有效:

val xs = List(1, 2, 3) :: List('a, 'b) :: List("X", "Y") :: List(4, 5) :: HNil
val test = magic(xs)

And then: 接着:

scala> test == List(1, 2, 3, 4, 5) :: List('a, 'b) :: List("X", "Y") :: HNil
res0: Boolean = true

As expected. 正如所料。

The code above is written for 1.2.4, but it should work on 2.0 with a couple of very minor modifications. 上面的代码是针对1.2.4编写的,但它应该在2.0上进行一些非常小的修改。


Update: for the record, here's a working version for 2.0: 更新:记录,这是2.0的工作版本:

import shapeless._, ops.hlist.{ LeftFolder, Prepend, Replacer, Selector }

trait LowPriorityCombine extends Poly2 {
  implicit def notAlreadySeen[L <: HList, A, Out <: HList](implicit
    p: Prepend.Aux[L, List[A] :: HNil, Out]
  ): Case.Aux[L, List[A], Out] = at[L, List[A]](_ :+ _)
}

object combine extends LowPriorityCombine {
  implicit def alreadySeen[L <: HList, A, Out <: HList](implicit
    s: Selector[L, List[A]],
    r: Replacer.Aux[L, List[A], List[A], (List[A], Out)]
  ): Case.Aux[L, List[A], Out] = at[L, List[A]] {
    case (acc, as) => acc.updatedElem[List[A], Out](acc.select[List[A]] ++ as)
  }
}

def magic[L <: HList](l: L)(implicit f: LeftFolder[L, HNil, combine.type]) =
  l.foldLeft(HNil: HNil)(combine)

The main difference is the new imports, but you also need some other small changes because of the extra type parameter on updatedElem . 主要区别在于新导入,但由于updatedElem上的额外类型参数,您还需要进行一些其他小的更改。

Implemented for shapeless 2.0 , but works with minor modifications for 1.2.4 as well: 实现了shapeless 2.0 ,但也适用于1.2.4微小修改:

import shapeless._

// remove this import for v1.2.4
import shapeless.ops.hlist.{Filter, FilterNot, RightReducer}

// for v1.2.4 replace `Poly` with `Poly2` and `use` with `at`
object combine extends Poly {
  implicit def lists[T] = use((c : List[T], s : List[T]) => c ::: s)
}

trait ListGroup[In <: HList] {
  type Out <: HList
  def apply(l: In): Out
}

object ListGroup {
  implicit def hnil =
    new ListGroup[HNil] {
      type Out = HNil
      def apply(l: HNil) = l
    }

  implicit def hlist[T, Tail <: HList, NOut <: HList, F <: HList, Rest <: HList](
                 implicit f: Filter[List[T] :: Tail, List[T]] { type Out = F },
                          r: RightReducer[F, combine.type] { type Out = List[T] },
                          fn: FilterNot[List[T] :: Tail, List[T]] { type Out = Rest },
                          next: ListGroup[Rest] { type Out = NOut }) =
    new ListGroup[List[T] :: Tail] {
      type Out = List[T] :: NOut
      def apply(l: List[T] :: Tail): List[T] :: NOut =
        l.filter[List[T]].reduceRight(combine) :: next(l.filterNot[List[T]])
    }
}

def magic[L <: HList](l: L)(implicit g: ListGroup[L]) = g(l)

Usage: 用法:

val hl = List(1, 2, 3) :: List('a, 'b) :: List("aa", "bb", "cc") :: List(4, 5) :: List('c, 'd, 'e) :: HNil

magic(hl)
// List(1, 2, 3, 4, 5) :: List('a, 'b, 'c, 'd, 'e) :: List(aa, bb, cc) :: HNil

For v1.2.4 replace combine object with this implementation: 对于v1.2.4combine对象替换为此实现:

object combine extends Poly2 {
  implicit def lists[T] = at((c : List[T], s : List[T]) => c ::: s)
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM