2013-01-21 31 views
13

Używam shapeless w Scala, i chciałbym napisać funkcję allPairs, która zajmie dwóch HLists i zwrócić HList wszystkich par elementów. Na przykład:Tworzenie HList wszystkich par z dwóch HLists

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) 

Każdy pomysł, jak to zrobić?

Ponadto chciałbym podkreślić, że szukam funkcji , a nie wbudowanego bloku kodu.

+0

Dokładnie jakie rozróżnienie między "_function_" a "inlined block of code?" –

+1

@RandallSchulz: Zobacz różnicę pomiędzy pierwszą odpowiedzią Travis poniżej i jego ostateczną odpowiedzią za pomocą liftA2. – emchristiansen

Odpowiedz

16

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.

+2

To jest miłe. Czy mógłbyś pokazać, jak spakowałbyś to do metody przyjmującej xs i ys jako parametry? Próbowałem to zrobić w ten sposób i bardzo źle. –

+0

@ RégisJean-Gilles: To dobre pytanie - okazuje się, że będziesz potrzebował więcej maszyn, aby uogólnić to na metodę. Robię to teraz. –

+0

Pytanie prosi o funkcję; Myślę, że to jest trudne. – emchristiansen