2011-01-27 13 views
5

ho creato un metodo di magnaccia, collate, questo è utilizzabile da qualsiasi Traversable o qualsiasi tipo che può essere costretto ad un percorribile secondo il seguente esempio:Semplificare le annotazioni di tipo

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)) 
*/ 

pensare ad esso come l'empia uova di un'unione 'Twixt collect e partition, se si vuole ...

e' definita in questo modo:

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) 

Concettualmente, non è poi così scoraggiante una volta capito come funziona lo standard CanBuildFrom, ma sto scoprendo che è stato sopraffatto dalla norma, specialmente con gli impliciti.

So che potrei semplificare molto la logica di ResultX utilizzando una HList, ed è qualcosa che probabilmente farò, quindi quel pezzo di codice non mi preoccupa particolarmente.

so anche che avrei potuto fare la mia vita in modo significativo più facile se ero in grado di limitare Repr come un sottotipo di Traversable. Ma mi rifiuto di farlo, perché non può essere usato contro le stringhe. Allo stesso modo, vorrei anche evitare di forzare le funzioni parziali a restituire un sottotipo di T - anche se questo è meno preoccupante in quanto potrei sempre infrangere la mia logica in operazioni distinte di collazione e mappa.

Altro che riguarda è CanBuildFrom[Repr, T, Repr], che mi sembra di continuare a ripetere, e che oscura le cose importanti dal mio metodo di firme. Questo è qualcosa che ritengo possa essere definito solo una volta a livello di classe, ma non ho ancora trovato un modo per farlo funzionare.

Qualche idea?

+0

Vorrei rinominare 'andThen' a' orElse'. –

+0

Ad ogni modo, ho adorato questo metodo. –

+0

@Daniel - Prenderò il nome sotto consiglio, anche se non sono convinto che 'orElse' dia il giusto senso di concatenare le operazioni insieme. La mia preferenza sarebbe 'then', che è già stata presa come parola chiave, e sono ancora indecisa su' + '. –

risposta

1

basta definire i tipi:

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) 
    } 
} 

D'altra parte, ho appena notato che l'intera Collatable è parametrizzata su Repr e T, quindi perché non si ottiene l'implicita remainderBuilder a quel livello? Modifica Perché T non è stato ancora dedotto. Per ora, non so come liberarmi degli extra impliciti.

+0

Ho provato a spingere il builder fino al livello di classe e spostando la vista vincolata in un implicito dedicato, come nel tuo secondo esempio. Stranamente, non stava risolvendo il costruttore quando l'ho fatto. Poi di nuovo ... ho notato che hai specificato 'remainderBuilder' prima di' ev' - l'ordine opposto alle mie prove. Potrebbe essere davvero così semplice? –

+0

Ovviamente, potrebbe anche essere che ho scoperto un bug nel compilatore che lo interrompe funzionando come dovrebbe! Questo è sicuramente abbastanza vicino al margine sanguinante del sistema di tipi per rendere possibile ciò ... –

+0

Il tipo di aliasing funziona, e certamente pulisce il codice un po ', ma l'ordinamento dei parametri non fa differenza. Se spingo il builder all'ambito della classe, la cosa viene compilata, ma la risoluzione implicita fallisce nel tentativo di usarla. Definire il costruttore usando 'implicitamente []' all'interno della classe ha lo stesso problema.Ancora perplesso, vedrò se ora fa la differenza compilando invece di usare REPL. –