【问题标题】:Parameterized folding on a shapeless HList无形 HList 上的参数化折叠
【发布时间】:2020-01-01 08:43:25
【问题描述】:

我正在尝试实现一种对调用者提供的 HList 进行参数化折叠的方法。 HList 可以有任意数量的相同类型的元素 (> 0)。

val list = "a" :: "b" :: "c" :: HNil

def process[L <: HList](mul: Int, l: L) = {
  object combine extends Poly2 {
    implicit def work = at[String, (Int, L)] {
      case (a, (b, acc)) => (b, (a * b) :: acc)
    }
  }
  l.foldRight((mul, HNil))(combine)._2
}

process(3, list)    //  expecting to get aaa :: bbb :: ccc :: HNil

我得到的是关于缺少隐式的错误:“找不到参数文件夹的隐式值:shapeless.ops.hlist.RightFolder[L,(Int, shapeless.HNil.type),combine.type]”。从this answer 可以清楚地看出,编译器希望看到它可以折叠 HList 的证据。

但是我不能将 RightFolder 作为隐式参数传递,因为 Poly2 类型在方法之外是未知的。即使有可能,隐式参数也只会在调用堆栈中进一步向上传播。事实上,我什至不希望调用者知道该方法是否执行折叠、映射、归约或其他任何操作。它需要提供的只是 HList 是正确的 HList 类型的证据。我认为问题在于 [L <: hlist>

以下代码按预期工作,但显然没有将折叠逻辑封装在方法中:

val list = "a" :: "b" :: "c" :: HNil

object combineS extends Poly2 {
  implicit def work[L <: HList] = at[String, (Int, L)] {
    case (a, (b, acc)) => (b, (a * b) :: acc)
  }
}

list.foldRight((3, HNil))(combineS)._2

【问题讨论】:

    标签: scala implicit shapeless hlist


    【解决方案1】:

    最简单的方法是提取combine(添加类型参数L)并将必要的隐式参数添加到process

    object combine extends Poly2 {
      implicit def work[L <: HList] = at[String, (Int, L)] {
        case (a, (b, acc)) => (b, (a * b) :: acc)
      }
    }
    
    def process[L <: HList](mul: Int, l: L)(implicit rightFolder: RightFolder.Aux[L, (Int, HNil.type), combine.type, _ <: (_,_)]) = {
      l.foldRight((mul, HNil))(combine)._2
    }
    

    即使有可能,隐式参数也只会在调用堆栈中进一步向上传播。事实上,我什至不希望调用者知道该方法是否执行折叠、映射、归约或其他任何操作。

    使用类型级编程,您可以将逻辑封装在类型类而不是方法中。所以你可以引入一个类型类

    trait Process[L <: HList] {
      type Out <: HList
      def apply(mul: Int, l: L): Out
    }
    object Process {
      type Aux[L <: HList, Out0 <: HList] = Process[L] { type Out = Out0 }
    
      object combine extends Poly2 {
        implicit def work[L <: HList] = at[String, (Int, L)] {
          case (a, (b, acc)) => (b, (a * b) :: acc)
        }
      }
    
      implicit def mkProcess[L <: HList, Res, A, L1 <: HList](implicit
        rightFolder: RightFolder.Aux[L, (Int, HNil.type), combine.type, Res],
        ev: Res <:< (A, L1)
      ): Aux[L, L1] = new Process[L] {
        override type Out = L1
        override def apply(mul: Int, l: L): Out = l.foldRight((mul, HNil))(combine)._2
      }
    }
    
    def process[L <: HList](mul: Int, l: L)(implicit p: Process[L]): p.Out = p(mul, l)
    

    【讨论】:

    • Dmytro,显然我所有的问题都是通过相同或相似的模式解决的。显然还有一些类型别名命名约定,例如 Out、Aux 等。你能推荐任何文章来阅读它吗?
    • 谢谢。我以前读过它,但显然它大部分都超出了我的想象。将不得不重新阅读。
    猜你喜欢
    • 1970-01-01
    • 2018-10-29
    • 1970-01-01
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多