2011-01-27 8 views
5

Utworzyłem metodę alfons collate, że jest użyteczny z dowolnego Traversable lub dowolnego typu, który może być zmuszany do przesuwny, jak na poniższym przykładzie:Uproszczenie adnotacje typu

val ints = List(0,9,4,5,-3,-5,6,5,-2,1,0,6,-3,-2) 
val results = ints collate { 
    case i: Int if(i < 0) => i.floatValue 
} andThen { 
    case i: Int if(i>5) => i.toString 
} andThen { 
    case i: Int if(i==0) => i 
} toTuple 

/* 
results: (List[Float], List[java.lang.String], List[Int], List[Int]) = 
(List(-3.0, -5.0, -2.0, -3.0, -2.0),List(9, 6, 6),List(0, 0),List(4, 5, 5, 1)) 
*/ 

myśleć o tym jak bezbożni tarło unii „twixt collect i partition, jeśli będzie ...

Jest zdefiniowany następująco:

import collection.generic.CanBuildFrom 

class Collatable[Repr <% Traversable[T], T](xs: Repr) { 

    // Results handling stuff, bit like a poor-man's HList, feel free to skip... 

    trait Results { 
    def remainder: Repr 

    type Append[That] <: Results 
    def append[That](tup: (That, Repr)): Append[That] 

    def andThen[R, That](pf: PartialFunction[T, R]) 
    (implicit 
     matchesBuilder: CanBuildFrom[Repr, R, That], 
     remainderBuilder: CanBuildFrom[Repr, T, Repr] 
    ) = { 
     val more = (new Collatable[Repr,T](remainder)).collateOne[R,That](pf) 
     append(more) 
    } 
    } 

    case class Results9[M1,M2,M3,M4,M5,M6,M7,M8,M9](
    m1: M1, m2: M2, m3: M3, m4: M4, m5: M5, m6: M6, m7: M7, m8: M8, m9: M9, 
    remainder: Repr) 
    extends Results { 
    implicit def toTuple = (m1, m2, m3, m4, m5, m6, m7, m8, m9, remainder) 
    def append[That](tup: (That, Repr)) = error("too many") 
    } 

    // ... skip a bit, still in results logic ... 

    case class Results2[M1,M2](
    m1: M1, m2: M2, remainder: Repr) 
    extends Results { 
    implicit def toTuple = (m1, m2, remainder) 
    type Append[That] = Results3[M1,M2,That] 
    def append[That](tup: (That, Repr)) = Results3(m1, m2, tup._1, tup._2) 
    } 

    case class Results1[M1](matches: M1, remainder: Repr) extends Results { 
    implicit def toTuple = (matches, remainder) 

    type Append[That] = Results2[M1, That] 
    def append[That](tup: (That, Repr)) = Results2(matches, tup._1, tup._2) 
    } 

    // and now... Our feature presentation! 

    def collateOne[R, That](pf: PartialFunction[T, R]) 
    (implicit 
    matchesBuilder: CanBuildFrom[Repr, R, That], 
    remainderBuilder: CanBuildFrom[Repr, T, Repr] 
) = { 
    val matches = matchesBuilder(xs) 
    val remainder = remainderBuilder(xs) 
    for (x <- xs) if (pf.isDefinedAt(x)) matches += pf(x) else remainder += x 
    (matches.result, remainder.result) 
    } 

    def collate[R, That](pf: PartialFunction[T, R]) 
    (implicit 
    matchesBuilder: CanBuildFrom[Repr, R, That], 
    remainderBuilder: CanBuildFrom[Repr, T, Repr] 
): Results1[That] = { 
    val tup = collateOne[R,That](pf) 
    Results1(tup._1, tup._2) 
    } 
} 

object Collatable { 
    def apply[Repr, T](xs: Repr)(implicit witness: Repr => Traversable[T]) = { 
    new Collatable[Repr, T](xs) 
    } 
} 

implicit def traversableIsCollatable[CC[X] <: Traversable[X], A](xs: CC[A]) = 
    Collatable[CC[A], A](xs) 
implicit def stringIsCollatable(xs: String) = 
    Collatable[String, Char](xs) 

Pojęciowo, nie jest to aż tak zniechęcające, gdy zrozumiesz, jak działa CanBuildFrom, ale stwierdzam, że jest on przytłoczony bojową tabliczką - zwłaszcza z implikacjami.

Wiem, że mogłem uprościć logikę ResultX za pomocą HList, i to jest coś, co prawdopodobnie zrobię, więc ten kawałek kodu nie martwi mnie specjalnie.

wiem też, że mogę uczynić moje życie znacznie łatwiej, gdybym był w stanie zahamować Repr jako podtyp Traversable. Ale odmawiam robienia tego, ponieważ wtedy nie można go użyć przeciwko Stringsowi. Na tej samej zasadzie chciałbym również uniknąć wymuszania funkcji częściowych, aby zwróciły podtyp T - chociaż jest to mniej niepokojące, ponieważ zawsze mogłem podzielić moją logikę na odrębne operacje sortowania i mapowania.

Więcej informacji na ten temat to CanBuildFrom[Repr, T, Repr], które zdaje się powtarzać, a które zaciemnia ważne informacje z podpisów metod. Jest to coś, co na pewno mogę zdefiniować tylko raz na poziomie klasy, ale jeszcze nie znalazłem sposobu, aby to zadziałało.

Wszelkie pomysły?

+0

Zmieniłbym nazwę 'and Then" na "orElse". –

+0

Nawiasem mówiąc, kochałem tę metodę. –

+0

@Daniel - Wezmę nazwisko pod radą, chociaż nie jestem przekonany, że 'orElse' daje właściwe poczucie łączenia działań razem. Moją preferencją byłoby "wtedy", które jest już brane jako słowo kluczowe i nadal nie jestem niezdecydowany co do '+'. –

Odpowiedz

1

wystarczy zdefiniować typy:

class Collatable[Repr <% Traversable[T], T](xs: Repr) { 

    // Results handling stuff, bit like a poor-man's HList, feel free to skip... 

    type With[-Elem] = CanBuildFrom[Repr, Elem, Repr] 
    type CanBuild[-Elem, +To] = CanBuildFrom[Repr, Elem, To] 

    trait Results { 
    def remainder: Repr 

    type Append[That] <: Results 
    def append[That](tup: (That, Repr)): Append[That] 

    def andThen[R, That](pf: PartialFunction[T, R]) 
    (implicit 
     matchesBuilder: CanBuild[R, That], 
     remainderBuilder: With[T] 
    ) = { 
     val more = (new Collatable[Repr,T](remainder)).collateOne[R,That](pf) 
     append(more) 
    } 
    } 

    def collateOne[R, That](pf: PartialFunction[T, R]) 
    (implicit 
    matchesBuilder: CanBuild[R, That], 
    remainderBuilder: With[T] 
) = { 
    val matches = matchesBuilder(xs) 
    val remainder = remainderBuilder(xs) 
    for (x <- xs) if (pf.isDefinedAt(x)) matches += pf(x) else remainder += x 
    (matches.result, remainder.result) 
    } 
} 

Z drugiej strony, po prostu zauważyłem, że cała Collatable jest parametryzowane na Repr i T, więc dlaczego nie można uzyskać niejawny remainderBuilder na tym poziomie? Edytuj Ponieważ T nie został jeszcze wywnioskowany. Na razie nie wiem, jak pozbyć się dodatkowych implicite.

+0

Próbowałem popychając budowniczego do poziomu klasy i przeniesienie widoku do dedykowanego, niejawnego, jak na twój drugi przykład. Dziwnie, po prostu nie rozwiązałem budowniczego, kiedy to zrobiłem. Z drugiej strony ... Zauważam, że określiłeś 'remainderBuilder' przed' ev' - kolejność odwrotną do moich prób. Czy to naprawdę może być takie proste? –

+0

Oczywiście może się zdarzyć, że znalazłem błąd w kompilatorze, który zatrzymuje działanie tak, jak powinno! Jest to z pewnością wystarczająco bliskie krwawiącemu brzegowi systemu typów, aby to umożliwić ... –

+0

Aliasowanie typu działa, a na pewno czyści trochę kod, ale uporządkowanie parametrów nie ma znaczenia. Jeśli przeciągnę ten builder do zasięgu klasy, kompiluje się, ale niejawna rozdzielczość kończy się niepowodzeniem, gdy próbuje go użyć. Definiowanie konstruktora przy użyciu 'niejawnie []' w klasie ma ten sam problem.Wciąż zakłopotany, zobaczę, czy teraz robi różnicę, kompilując zamiast używać REPL. –