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
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! –
BTW, byłbym bardzo zainteresowany, aby zobaczyć, jak używasz bezkształtny w swoim projekcie ... link github? –
@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