Подтвердить что ты не робот

Создание HList всех пар из двух HLists

Я использую бесформенность в Scala, и я бы хотел написать функцию allPairs, которая возьмет два HList и вернет 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)

Любая идея, как это сделать?

Кроме того, я хотел бы подчеркнуть, что ищу функцию, а не встроенный блок кода.

4b9b3361

Ответ 1

Вы не можете использовать for -понимание или комбинацию 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

Что дает нам следующее (правильно набранное):

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

Написание метода, который будет делать это с аргументами HList, сложнее. Вот краткий пример того, как это можно сделать (с немного более общим оборудованием).

Начну с того, что мы можем думать о том, чтобы найти декартово произведение двух обычных списков как "снятие" функции, которая принимает два аргумента и возвращает их как кортеж в аппликативный функтор для списков. Например, вы можете написать следующее в Haskell:

import Control.Applicative (liftA2)

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

Мы можем написать полиморфную двоичную функцию, которая соответствует (,) здесь:

import shapeless._

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

И снова определим наши списки примеров для полноты:

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

Теперь мы будем работать с методом с именем liftA2, который позволит нам написать следующее:

liftA2(tuple)(xs, ys)

И получите правильный результат. Имя liftA2 немного вводит в заблуждение, поскольку на самом деле у нас нет экземпляра прикладного функтора, и поскольку он не является общим - я работаю над моделью методов с именем flatMap и map на HList, и я открыт для предложений о чем-то лучшем.

Теперь нам нужен класс типа, который позволит нам взять 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)
  }
}

И теперь класс типа, который поможет с поднятием:

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

И, наконец, наш метод:

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

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

Как мы и хотели.