2016-02-01 11 views
6

Ich habe eine Art von komplexen Typ-Hierarchie, aber um es zu brechen gibt es zwei grundlegende Merkmale: Convertable und Conversion[A <: Convertable, B <: Convertable, z. Es gibt eine Konvertierung, die einen Mealy-Automaten in einen Moore-Automaten umwandeln kann. Jede hat eine convert(automaton: A) : B Methode.Umklappen HList mit unbekannten Typen

Jetzt möchte ich das Konzept Smart Conversions vorstellen, die im Grunde eine Liste der normalen Conversions sind, die nacheinander durchgeführt werden. Deshalb habe ich eine AutoConversion Eigenschaft eingeführt, die eine Conversion erweitert, die einen val path : HList Parameter hat, um die Kette von Konvertierungen darzustellen, und sollte die convert Methode implementieren, so dass AutoConversion nur die Liste der tatsächlichen Conversions bereitstellen muss. Ich glaube, Sie dies mit einem fold über die path implementieren könnten, also hier ist mein erster Versuch:

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

Das wird nicht funktionieren, weil keine impliziten Ordner gefunden werden können, Ich vermute, dass ich irgendwo mehr Typinformationen für den Compiler bereitstellen muss, aber ich weiß nicht wo

Antwort

9

Sie haben recht, wenn Sie mehr Typinformationen benötigen, und im Allgemeinen, wenn Sie einen Wert mit HList als haben ein statischer Typ, ist es wahrscheinlich, dass Sie Ihren Ansatz ändern müssen. Es gibt im Wesentlichen nichts, was Sie mit einem HList tun können, wenn alles, was Sie wissen, ist, dass es ein HList ist (neben den Werten vor), und Sie werden immer nur HList als Typ Einschränkung schreiben.

In Ihrem Fall ist das, was Sie beschreiben, eine Art typisierte Sequenz. Bevor Sie mit diesem Ansatz fortfahren, würde ich vorschlagen, dass Sie wirklich sicher sind, dass Sie das wirklich tun müssen. Eines der schönen Dinge über Funktionen (und Funktion ähnliche Typen wie Ihre Conversion) ist, dass sie komponieren: Sie haben eine A => B und eine B => C und Sie komponieren sie in eine A => C und können über B für immer vergessen. Sie erhalten eine schöne, saubere schwarze Box, die in der Regel genau das ist, was Sie wollen.

In einigen Fällen kann es jedoch nützlich sein, funktionsähnliche Dinge so zu komponieren, dass Sie über die Teile der Pipeline nachdenken können. Ich gehe davon aus, dass dies einer dieser Fälle ist, aber Sie sollten das für sich selbst bestätigen. Wenn nicht, hast du Glück, denn was kommt, ist irgendwie unordentlich.

Ich werde diese Typen annehmen:

trait Convertable 

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

Wir können eine Typklasse definieren, die Zeugen, dass eine bestimmte HList von einem oder mehreren Umwandlungen, deren Typen in einer Reihe aufstellen zusammen:

import shapeless._ 

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

L enthält alle Typinformationen über die Pipeline, und I und O sind die Typen seiner Endpunkte.

Als nächstes müssen wir Instanzen für diese Klasse (beachten Sie, dass dies muss zusammen mit dem obigen Merkmale definiert werden, für die zwei companioned werden):

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

Und jetzt können Sie eine Version Ihrer AutoConversion schreiben, dass verfolgt alle von der Art, Informationen über die Pipeline:

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

Und Sie es wie folgt verwenden können:

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) 

Und conv haben den erwarteten statischen Typ (und implementieren Conversion[AutoA, AutoC]).

Verwandte Themen