将无形HList转换为较小的HList

 BELLICOSE牛仔 发布于 2023-02-09 18:48

我有一个无形的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

我想转换成一个更简单的类型,其中从左到右追加相同类型的列表:

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

为什么在无形v1.2.4中有一些内置功能吗?

2 个回答
  • 虽然在另一个答案(引入一个新的类型)中采用的方法将起作用,但是可以使用更通用的机器来解决这个问题 - 并且与在价值水平上解决类似问题的方式没有什么不同.

    我们将使用左折.编写组合函数有点棘手,因为我们有两种情况(我们已经看到一个与当前元素具有相同类型的元素,或者我们没有),我们必须使用隐式优先级技巧来避免模糊隐含值:

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

    但后来我们基本上完成了:

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

    我们可以证明它有效:

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

    然后:

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

    正如所料.

    上面的代码是针对1.2.4编写的,但它应该在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)
    

    主要区别在于新的导入,但由于额外的类型参数,您还需要一些其他小的更改updatedElem.

    2023-02-09 18:50 回答
  • 已实现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)
    

    用法:

    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
    

    对于使用此实现的v1.2.4替换combine对象:

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

    2023-02-09 18:50 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有