从两个HList创建所有对的HList

emc*_*sen 14 scala hlist shapeless

我在Scala中使用无形,我想编写一个函数allPairs,它将获取两个HLists并返回所有元素对的HList.例如:

import shapeless._
val list1 = 1 :: "one" :: HNil
val list2 = 2 :: "two" :: HNil
// Has value (1, 2) :: (1, "two") :: ("one", 2) :: ("one", "two") :: HNil
val list3 = allPairs(list1, list2)
Run Code Online (Sandbox Code Playgroud)

知道怎么做吗?

另外,我想强调一下我正在寻找一个函数,而不是一个内联的代码块.

Tra*_*own 17

你不能使用for-comprehension或组合map,并flatMap与这里的功能文字(如其他答案建议),因为这些方法HList需要更高级别的功能.如果您只有两个静态类型列表,这很容易:

import shapeless._

val xs = 1 :: 'b :: 'c' :: HNil
val ys = 4.0 :: "e" :: HNil

object eachFirst extends Poly1 {
  implicit def default[A] = at[A] { a =>
    object second extends Poly1 { implicit def default[B] = at[B](a -> _) }
    ys map second
  }
}

val cartesianProductXsYs = xs flatMap eachFirst
Run Code Online (Sandbox Code Playgroud)

这给了我们以下(适当打字):

(1,4.0) :: (1,e) :: ('b,4.0) :: ('b,e) :: (c,4.0) :: (c,e) :: HNil
Run Code Online (Sandbox Code Playgroud)

编写一个用HList参数执行此操作的方法比较棘手.这是一个如何完成它的快速示例(使用一些稍微更通用的机器).

我首先要注意的是,我们可以想到找到两个普通列表的笛卡尔积,作为"提升"一个带有两个参数的函数,并将它们作为元组返回到列表的应用函子中.例如,您可以在Haskell中编写以下内容:

import Control.Applicative (liftA2)

cartesianProd :: [a] -> [b] -> [(a, b)]
cartesianProd = liftA2 (,)
Run Code Online (Sandbox Code Playgroud)

我们可以写一个对应于(,)这里的多态二进制函数:

import shapeless._

object tuple extends Poly2 {
  implicit def whatever[A, B] = at[A, B] { case (a, b) => (a, b) }
}
Run Code Online (Sandbox Code Playgroud)

并再次定义我们的示例列表以获得完整性:

val xs = 1 :: 'b :: 'c' :: HNil
val ys = 4.0 :: "e" :: HNil
Run Code Online (Sandbox Code Playgroud)

现在我们将开发一个名为的方法liftA2,它将允许我们编写以下内容:

liftA2(tuple)(xs, ys)
Run Code Online (Sandbox Code Playgroud)

并获得正确的结果.这个名称liftA2有点误导,因为我们没有真正的应用程序仿函数实例,并且因为它不是通用的 - 我正在研究名为flatMapand mapon 的方法的模型HList,并且愿意接受更好的建议.

现在我们需要一个类型类,它允许我们将a Poly2部分应用于某个东西,并将得到的一元函数映射到HList:

trait ApplyMapper[HF, A, X <: HList, Out <: HList] {
  def apply(a: A, x: X): Out
}

object ApplyMapper {
  implicit def hnil[HF, A] = new ApplyMapper[HF, A, HNil, HNil] {
    def apply(a: A, x: HNil) = HNil
  }
  implicit def hlist[HF, A, XH, XT <: HList, OutH, OutT <: HList](implicit
    pb: Poly.Pullback2Aux[HF, A, XH, OutH],
    am: ApplyMapper[HF, A, XT, OutT]
  ) = new ApplyMapper[HF, A, XH :: XT, OutH :: OutT] {
    def apply(a: A, x: XH :: XT) = pb(a, x.head) :: am(a, x.tail)
  }
}
Run Code Online (Sandbox Code Playgroud)

现在是一个类型类来帮助解除:

trait LiftA2[HF, X <: HList, Y <: HList, Out <: HList] {
  def apply(x: X, y: Y): Out
}

object LiftA2 {
  implicit def hnil[HF, Y <: HList] = new LiftA2[HF, HNil, Y, HNil] {
    def apply(x: HNil, y: Y) = HNil
  }

  implicit def hlist[
    HF, XH, XT <: HList, Y <: HList,
    Out1 <: HList, Out2 <: HList, Out <: HList
  ](implicit
    am: ApplyMapper[HF, XH, Y, Out1],
    lift: LiftA2[HF, XT, Y, Out2],
    prepend : PrependAux[Out1, Out2, Out]
  ) = new LiftA2[HF, XH :: XT, Y, Out] {
    def apply(x: XH :: XT, y: Y) = prepend(am(x.head, y), lift(x.tail, y))
  }
}
Run Code Online (Sandbox Code Playgroud)

最后我们的方法本身:

def liftA2[HF, X <: HList, Y <: HList, Out <: HList](hf: HF)(x: X, y: Y)(implicit
  lift: LiftA2[HF, X, Y, Out]
) = lift(x, y)
Run Code Online (Sandbox Code Playgroud)

而这一切现在都liftA2(tuple)(xs, ys)有效.

scala> type Result =
     |   (Int, Double) :: (Int, String) ::
     |   (Symbol, Double) :: (Symbol, String) ::
     |   (Char, Double) :: (Char, String) :: HNil
defined type alias Result

scala> val res: Result = liftA2(tuple)(xs, ys)
res: Result = (1,4.0) :: (1,e) :: ('b,4.0) :: ('b,e) :: (c,4.0) :: (c,e) :: HNil
Run Code Online (Sandbox Code Playgroud)

就像我们想要的那样.

  • 这很好.你能说明如何将xs和ys作为参数打包成一个方法吗?我试着这样做而且非常失败. (2认同)
  • 干得好.我希望我能再次投票.然后,我不确定我是否应该高兴看到结果有多复杂.我猜想元编程的乐趣和痛苦.顺便说一句,有什么文件可以找到无形的吗?尝试解决这个难题的部分困难在于反向设计Pullback1Aux,Case1,Case2以及生活在无形动物园中的所有有趣动物的目的. (2认同)