2013-08-27 27 views
13

Pytanie

Czy jest jakiś sposób można utworzyć wyciąg dla bezkształtne HList że wygląda następująco.Extractor dla bezkształtną HList który naśladuje parser konkatenacji

val a ~ _ ~ b = 4 :: "so" :: 4.5 :: HNil 
=> a == 4 && b == 4.5 
  1. Wymień :: przez ~, która nie powinna być problemem.
  2. Pozbądź się terminacji HNil. Czy są jakieś problemy, które mogą się pojawić?

Motywacja

Po wielu potu i łez udało mi się dotrzeć do punktu, w którym poniższy kod działa:

for(
    x1 :: _ :: x2 :: HNil <- (expInt ~ "+" ~ expInt).llE 
) yield (x1 + x2) 

expInt analizuje Int w pewnym monady E. Typ (expInt ~ "+" ~ expInt).llE to E[Int :: String :: Int :: HNil].

Chcę, aby wzór po lewej stronie <- był jakoś podobny do konstrukcji parsera kombinator po prawej stronie.

+0

Na pierwszy rzut oka jest to, że moje podejrzenie, że składnia wyciąg nie będzie możliwa , ale jest to godny cel i życzę ci szczęścia! –

+0

BTW, byłbym bardzo zainteresowany, aby zobaczyć, jak używasz bezkształtny w swoim projekcie ... link github? –

+0

@Miles Obecnie jest to duży bałagan. Właśnie wyodrębniłem funkcjonalność, nad którą pracuję, do własnej biblioteki i jestem w trakcie sortowania. Planuję umieścić go na Githubie, gdy tylko nadarzy się okazja, że ​​ktoś może to zrozumieć. Będzie to biblioteka ułatwiająca tworzenie narzędzi wiersza poleceń (części parsera) do przeprowadzania eksperymentów obliczeniowych (część monady); jak rozwiązywanie problemów z różnymi algorytmami przy różnych parametrach. Przypominałem ci o mojej liście rzeczy do zrobienia. – ziggystar

Odpowiedz

35

Można to zrobić, i ma kilka interesujących zwrotów akcji.

Po pierwsze, typowe dopasowanie do struktury zbudowanej za pomocą prawego konstruktora asocjacyjnego (np. ::) wymagałoby użycia odpowiedniego asocjacyjnego ekstraktora, w przeciwnym razie rozkładałby się i wiązał wyodrębnione elementy w odwrotnej kolejności. Niestety, prawostronne ekstraktory muszą, podobnie jak prawe operatory asocjacyjne, kończyć się na : w Scali, co mogłoby spowodować oderwanie składni kombinatorowej parsera, ponieważ nazwa ekstraktora musiałaby być ~: zamiast zwykłego ~. Jednak odłożę to na chwilę i pracuję z prawą asocjatywnością.

Drugi skręt jest to, że musimy Wycofywanie metody przynosić różnego rodzaju, w zależności od tego, czy mamy do dopasowania się HList z więcej niż dwoma elementami lub z dokładnie dwóch elementów (i nie powinien być w stanie dopasować listę mniej niż dwóch elementów w ogóle).

Jeśli dopasowujemy listę więcej niż dwóch elementów, musimy rozłożyć listę na parę składającą się z głowy i ogona HList, tj. podając l: H :: T gdzie T <: HList musimy podać wartość typu (H, T). Jeśli z drugiej strony dopasowujemy listę dokładnie dwóch elementów, tj. w postaci E1 :: E2 :: HNil, musimy rozłożyć listę na parę składającą się tylko z tych dwóch elementów, tj. (E1, E2) zamiast głowy i ogona, które byłyby (E1, E2 :: HNil).

Można tego dokonać za pomocą dokładnie tego samego rodzaju technik programowania, które są używane w trybie bezkształtnym.Najpierw musimy zdefiniować klasę typu, który zrobi pracę wyciągu z przypadków odpowiadających każdej z dwóch przypadkach opisanych powyżej,

import shapeless._ 

trait UnapplyRight[L <: HList] extends DepFn1[L] 

trait LPUnapplyRight { 
    type Aux[L <: HList, Out0] = UnapplyRight[L] { type Out = Out0 } 
    implicit def unapplyHCons[H, T <: HList]: Aux[H :: T, Option[(H, T)]] = 
    new UnapplyRight[H :: T] { 
     type Out = Option[(H, T)] 
     def apply(l: H :: T): Out = Option((l.head, l.tail)) 
    } 
} 

object UnapplyRight extends LPUnapplyRight { 
    implicit def unapplyPair[H1, H2]: Aux[H1 :: H2 :: HNil, Option[(H1, H2)]] = 
    new UnapplyRight[H1 :: H2 :: HNil] { 
     type Out = Option[(H1, H2)] 
     def apply(l: H1 :: H2 :: HNil): Out = Option((l.head, l.tail.head)) 
    } 
} 

Następnie definiujemy naszą ściągacza pod względem niego, jak tak,

object ~: { 
    def unapply[L <: HList, Out](l: L) 
    (implicit ua: UnapplyRight.Aux[L, Out]): Out = ua(l) 
} 

A potem jesteśmy dobrze iść,

val l = 23 :: "foo" :: true :: HNil 

val a ~: b ~: c = l 
a : Int 
b : String 
c : Boolean 

tak daleko, tak dobrze. Teraz wróćmy do kwestii powiązania. Jeśli chcemy uzyskać ten sam efekt za pomocą lewego ekstraktu asocjacyjnego (tj. ~ zamiast ~:), musimy zmienić sposób, w jaki dokonuje się dekompozycja. Najpierw odwołajmy się do odpowiedniej asocjatywnej składni ekstraktów, którą właśnie wykorzystaliśmy. Wyrażenie

val a ~: b ~: c = l 

jest równoważna,

val ~:(a, ~:(b, c)) = l 

Natomiast lewa wersja asocjacyjne,

val a ~ b ~ c = l 

jest równoważna,

val ~(~(a, b), c) = l 

Aby to zrobić działa jako ekstraktor dla HLists nasza klasa typu unapply musi odrywać elementy od końca, a nie od początku listy. Możemy to zrobić za pomocą klas bezkształtne za Init i Last typ,

trait UnapplyLeft[L <: HList] extends DepFn1[L] 

trait LPUnapplyLeft { 
    import ops.hlist.{ Init, Last } 
    type Aux[L <: HList, Out0] = UnapplyLeft[L] { type Out = Out0 } 
    implicit def unapplyHCons[L <: HList, I <: HList, F] 
    (implicit 
     init: Init.Aux[L, I], 
     last: Last.Aux[L, F]): Aux[L, Option[(I, F)]] = 
    new UnapplyLeft[L] { 
     type Out = Option[(I, F)] 
     def apply(l: L): Out = Option((l.init, l.last)) 
    } 
} 

object UnapplyLeft extends LPUnapplyLeft { 
    implicit def unapplyPair[H1, H2]: Aux[H1 :: H2 :: HNil, Option[(H1, H2)]] = 
    new UnapplyLeft[H1 :: H2 :: HNil] { 
     type Out = Option[(H1, H2)] 
     def apply(l: H1 :: H2 :: HNil): Out = Option((l.head, l.tail.head)) 
    } 
} 

object ~ { 
    def unapply[L <: HList, Out](l: L) 
    (implicit ua: UnapplyLeft.Aux[L, Out]): Out = ua(l) 
} 

a teraz skończymy,

val a ~ b ~ c = l 
a : Int 
b : String 
c : Boolean