2016-02-01 18 views
6

Ho una specie di gerarchia di tipi complessi, ma per suddividerla ci sono due tratti base: Convertable e Conversion[A <: Convertable, B <: Convertable, ad es. c'è una Conversione che può convertire un automa Mealy in un automa di Moore. Ogni Conversion[A,B] ha un metodo convert(automaton: A) : B.Fold over HList con tipi sconosciuti

Ora voglio introdurre il concetto di conversioni intelligenti, che sono fondamentalmente un elenco di normali conversioni, che verranno eseguite una dopo l'altra. Quindi ho introdotto un tratto AutoConversion, estendendo una conversione, che ha un parametro val path : HList, per rappresentare la catena di conversioni e dovrebbe implementare il metodo convert, in modo che AutoConversions debba solo fornire l'elenco delle conversioni effettive da eseguire. Penso che si potrebbe implementare questo con un fold sopra la path, ecco il mio primo tentativo:

package de.uni_luebeck.isp.conversions 

import shapeless._ 
import shapeless.ops.hlist.LeftFolder 

trait AutoConversion[A <: Convertable, B <: Convertable] extends Conversion[A, B] { 
    val path: HList 

    object combiner extends Poly { 
     implicit def doSmth[C <: Convertable, D <: Convertable] = 
     use((conv : Conversion[C, D] , automaton : C) => conv.convert(automaton)) 

}

override def convert(startAutomaton: A): B = { 
    path.foldLeft(startAutomaton)(combiner) 
    } 
} 

questo non funzionerà, perché nessuna cartella implicita può essere trovato, quindi suppongo di dover fornire più informazioni sul tipo per il compilatore da qualche parte, ma non so dove sia

risposta

9

Hai ragione sul bisogno di più informazioni sul tipo, e in generale se hai un valore con HList come un tipo statico, è probabile che dovrai cambiare approccio. Non c'è praticamente nulla che tu possa fare con un HList se tutto quello che sai è che si tratta di un HList (oltre ad anteporre valori ad esso) e di solito scrivi sempre HList come un vincolo di tipo.

Nel tuo caso, quello che stai descrivendo è una specie di sequenza allineata al testo. Prima di andare avanti con questo approccio, suggerirei di essere davvero sicuro di aver effettivamente bisogno. Una delle cose belle delle funzioni (e dei tipi di funzioni come il tuo Conversion) è che esse compongono: tu hai un A => B e un B => C e li componi in un A => C e puoi dimenticarti di B per sempre. Ottieni una bella scatola nera pulita, che è generalmente esattamente ciò che desideri.

In alcuni casi, tuttavia, può essere utile essere in grado di comporre oggetti simili a funzioni in modo da poter riflettere sui pezzi della pipeline. Presumo che questo sia uno di quei casi, ma dovresti confermarlo per te stesso. Se non lo è, sei fortunato, perché quello che sta arrivando è un po 'disordinato.

darò per scontato questo tipo:

trait Convertable 

trait Conversion[A <: Convertable, B <: Convertable] { 
    def convert(a: A): B 
} 

possiamo definire una classe tipo che testimoni che una specifica HList è composto da una o più conversioni i cui tipi allineare:

import shapeless._ 

trait TypeAligned[L <: HList] extends DepFn1[L] { 
    type I <: Convertable 
    type O <: Convertable 
    type Out = Conversion[I, O] 
} 

L contiene tutte le informazioni sul tipo della pipeline e I e O sono i tipi dei relativi endpoint.

Poi abbiamo bisogno di esempi per questa classe di tipo (si noti che questo deve essere definito insieme con il tratto sopra per i due da companioned):

object TypeAligned { 
    type Aux[L <: HList, A <: Convertable, B <: Convertable] = TypeAligned[L] { 
    type I = A 
    type O = B 
    } 

    implicit def firstTypeAligned[ 
    A <: Convertable, 
    B <: Convertable 
    ]: TypeAligned.Aux[Conversion[A, B] :: HNil, A, B] = 
    new TypeAligned[Conversion[A, B] :: HNil] { 
     type I = A 
     type O = B 
     def apply(l: Conversion[A, B] :: HNil): Conversion[A, B] = l.head 
    } 

    implicit def composedTypeAligned[ 
    A <: Convertable, 
    B <: Convertable, 
    C <: Convertable, 
    T <: HList 
    ](implicit 
    tta: TypeAligned.Aux[T, B, C] 
): TypeAligned.Aux[Conversion[A, B] :: T, A, C] = 
    new TypeAligned[Conversion[A, B] :: T] { 
     type I = A 
     type O = C 
     def apply(l: Conversion[A, B] :: T): Conversion[A, C] = 
     new Conversion[A, C] { 
      def convert(a: A): C = tta(l.tail).convert(l.head.convert(a)) 
     } 
    } 
} 

E ora è possibile scrivere una versione del AutoConversion che tiene traccia di tutte le informazioni sul tipo sulla pipeline di:

class AutoConversion[L <: HList, A <: Convertable, B <: Convertable](
    path: L 
)(implicit ta: TypeAligned.Aux[L, A, B]) extends Conversion[A, B] { 
    def convert(a: A): B = ta(path).convert(a) 
} 

E si può usare in questo modo:

case class AutoA(i: Int) extends Convertable 
case class AutoB(s: String) extends Convertable 
case class AutoC(c: Char) extends Convertable 

val ab: Conversion[AutoA, AutoB] = new Conversion[AutoA, AutoB] { 
    def convert(a: AutoA): AutoB = AutoB(a.i.toString) 
} 

val bc: Conversion[AutoB, AutoC] = new Conversion[AutoB, AutoC] { 
    def convert(b: AutoB): AutoC = AutoC(b.s.lift(3).getOrElse('-')) 
} 

val conv = new AutoConversion(ab :: bc :: HNil) 

E conv avrà il tipo statico previsto (e implementare Conversion[AutoA, AutoC]).