从两个 HList 创建所有对的 HList [英] Creating an HList of all pairs from two HLists

查看:59
本文介绍了从两个 HList 创建所有对的 HList的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm using shapeless in Scala, and I'd like to write a function allPairs that will take two HLists and return an HList of all pairs of elements. For example:

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)

知道怎么做吗?

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

Also, I'd like to emphasize I'm looking for a function, not an inlined block of code.

推荐答案

你不能使用 for-comprehension 或 mapflatMap 的组合 在这里使用函数文字(正如其他答案所建议的那样),因为 HList 上的这些方法需要 更高等级的函数.如果您只有两个静态类型列表,这很容易:

You can't use a for-comprehension or a combination of map and flatMap with function literals here (as the other answers suggest), since these methods on HList require higher rank functions. If you just have two statically typed lists, this is easy:

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

这为我们提供了以下内容(正确键入):

Which gives us the following (appropriately typed):

(1,4.0) :: (1,e) :: ('b,4.0) :: ('b,e) :: (c,4.0) :: (c,e) :: HNil

编写一个使用 HList 参数执行此操作的方法比较棘手.下面是一个简单的例子,说明如何做到这一点(使用一些更通用的机制).

Writing a method that will do this with HList arguments is trickier. Here's a quick example of how it can be done (with some slightly more general machinery).

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

I'll start by noting that we can think of finding the Cartesian product of two ordinary lists as "lifting" a function that takes two arguments and returns them as a tuple into the applicative functor for lists. For example, you can write the following in Haskell:

import Control.Applicative (liftA2)

cartesianProd :: [a] -> [b] -> [(a, b)]
cartesianProd = liftA2 (,)

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

We can write a polymorphic binary function that corresponds to (,) here:

import shapeless._

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

为了完整性再次定义我们的示例列表:

And define our example lists again for completeness:

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

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

Now we'll work toward a method named liftA2 that will allow us to write the following:

liftA2(tuple)(xs, ys)

并得到正确的结果.liftA2 这个名字有点误导,因为我们没有真正的应用函子实例,而且因为它不是通用的——我正在研究名为 flatMap 的方法的模型HList 上的 code> 和 map,我愿意接受更好的建议.

And get the correct result. The name liftA2 is a little misleading, since we don't really have an applicative functor instance, and since it's not generic—I'm working on the model of the methods named flatMap and map on HList, and am open to suggestions for something better.

现在我们需要一个类型类,它允许我们使用 Poly2,将其部分应用于某物,并将生成的一元函数映射到 HList 上:

Now we need a type class that will allow us to take a Poly2, partially apply it to something, and map the resulting unary function over an 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)
  }
}

现在有一个类型类来帮助提升:

And now a type class to help with the lifting:

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

最后是我们的方法本身:

And finally our method itself:

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)

这就是全部——现在 liftA2(tuple)(xs, ys) 起作用了.

And that's all—now liftA2(tuple)(xs, ys) works.

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

正如我们所愿.

这篇关于从两个 HList 创建所有对的 HList的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆