从两个 HList 创建所有对的 HList [英] Creating an HList of all pairs from two HLists
问题描述
我在 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 或 map
和 flatMap 的组合
在这里使用函数文字(正如其他答案所建议的那样),因为 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屋!