Nie można użyć for
-comprehension lub kombinacji map
i flatMap
z literałów funkcyjnych tutaj (jak inne odpowiedzi sugerują), ponieważ te metody na HList
wymagają higher rank functions. Jeśli tylko mają dwa statycznie wpisane list, to jest proste:
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
co daje nam następujące (odpowiednio wpisane):
(1,4.0) :: (1,e) :: ('b,4.0) :: ('b,e) :: (c,4.0) :: (c,e) :: HNil
Pisanie metodę, która będzie to zrobić z HList
argumentów jest trudniejsze. Oto krótki przykład tego, jak można to zrobić (z nieco bardziej ogólną maszynerią).
Zacznę od stwierdzenia, że możemy pomyśleć o znalezieniu kartezjańskiego produktu dwóch zwykłych list jako "podnoszącego" funkcję, która pobiera dwa argumenty i zwraca je jako krotkę do funktora aplikacyjnego dla list. Na przykład, można napisać the following in Haskell:
import Control.Applicative (liftA2)
cartesianProd :: [a] -> [b] -> [(a, b)]
cartesianProd = liftA2 (,)
Możemy napisać polimorficzny binarną funkcję odpowiadającą (,)
tutaj:
import shapeless._
object tuple extends Poly2 {
implicit def whatever[A, B] = at[A, B] { case (a, b) => (a, b) }
}
i zdefiniować nasz przykład wymienia ponownie dla kompletności:
val xs = 1 :: 'b :: 'c' :: HNil
val ys = 4.0 :: "e" :: HNil
Teraz będziemy pracować nad metodą o nazwie liftA2
, która pozwoli nam napisać:
liftA2(tuple)(xs, ys)
I uzyskaj poprawny wynik. Nazwa liftA2
jest trochę myląca, ponieważ tak naprawdę nie mamy aplikacyjnej instancji funktora, a ponieważ nie jest ona generyczna - pracuję nad modelem metod o nazwach flatMap
i map
na HList
i jestem otwarty na sugestie dotyczące czegoś lepszy.
Teraz musimy klasę typu, który pozwoli nam podjąć Poly2
, częściowo stosuje się go do czegoś, a map wynikowy funkcji jednoargumentowy nad 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)
}
}
a teraz klasa typu, aby pomóc zniesienie:
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))
}
}
I wreszcie nasza metoda sama:
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)
A to wszystko-teraz działa 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
Tak jak chcieliśmy.
Dokładnie jakie rozróżnienie między "_function_" a "inlined block of code?" –
@RandallSchulz: Zobacz różnicę pomiędzy pierwszą odpowiedzią Travis poniżej i jego ostateczną odpowiedzią za pomocą liftA2. – emchristiansen