无形 HList 上的参数化折叠

Parameterized folding on a shapeless HList

我正在尝试实现一种方法,该方法对调用者提供的 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

我得到的是关于缺少隐式的错误:"could not find implicit value for parameter folder: 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

最简单的方法是提取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
}

And even if it were possible, the implicit parameter would only propagate further up the call stack. In fact I don't want the caller to even know whether the method performs folding, mapping, reduction or anything else.

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

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)