2016-02-01 11 views
6

Mam rodzaj złożonej hierarchii typów, ale aby ją złamać, istnieją dwie podstawowe cechy: Convertable i Conversion[A <: Convertable, B <: Convertable, np. istnieje Konwersja, która może przekształcić automat Mealy w automat Moore'a. Co Conversion[A,B] ma metodę convert(automaton: A) : B.Fold over HList z nieznanymi typami

Teraz chcę wprowadzić koncepcję smart Conversions, które są po prostu Listą zwykłych Konwersji, które będą wykonywane jeden po drugim. Dlatego wprowadziłem cechę AutoConversion, rozszerzając Konwersję, która ma parametr val path : HList, aby reprezentować łańcuch konwersji, i należy zaimplementować metodę convert, aby AutoConwersje musiały podać listę rzeczywistych Konwersji do wykonania. Myślę, że można zaimplementować to z fold nad path, więc tutaj jest moja pierwsza próba:

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

To nie będzie działać, ponieważ nie niejawna Folder można znaleźć, więc zgaduję, że muszę dostarczyć gdzieś więcej informacji o typie kompilatora, ale nie wiem, gdzie jest

Odpowiedz

9

Masz rację, żądając więcej informacji o typie, a ogólnie, jeśli masz wartość z HList jako typu statycznego, prawdopodobnie będziesz musiał zmienić swoje podejście. Zasadniczo nie można nic zrobić z HList, jeśli wszystko, co wiesz, to to, że jest to HList (oprócz tego, że przedkłada do niego wartości), i zazwyczaj piszesz tylko HList jako ograniczenie typu.

W twoim przypadku to, co opisujesz, jest rodzajem sekwencji wyrównanej do typu. Zanim zastosujesz to podejście, sugeruję, że naprawdę musisz to zrobić. Jedną z fajnych rzeczy na temat funkcji (i typów funkcjonalnych, takich jak Twoja Conversion) jest to, że komponują się: masz A => B i B => C, a Ty komponujesz je jako A => C i możesz na zawsze zapomnieć o B. Dostajesz ładną czystą czarną skrzynkę, która jest dokładnie tym, czego potrzebujesz.

W niektórych przypadkach przydatne może być komponowanie rzeczy funkcjonalnych w taki sposób, aby można było zastanowić się nad kawałkami potoku. Zakładam, że jest to jeden z tych przypadków, ale powinieneś to potwierdzić dla siebie. Jeśli nie, masz szczęście, bo to, co nadchodzi, jest trochę nieporządne.

będę zakładać te typy:

trait Convertable 

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

Możemy zdefiniować klasę typu, że świadkowie, że konkretny HList składa się z jednego lub większej liczby konwersji, których typy linii:

import shapeless._ 

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

L zawiera wszystkie informacje o typie potoku, a I i O są typami jego punktów końcowych.

Następnie musimy instancje tej klasy typu (zauważ, że to musi być zdefiniowane wraz z cechą wyżej dla dwóch do 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)) 
     } 
    } 
} 

I teraz można napisać wersję swojej AutoConversion że śledzi wszystkie informacje na temat typu rurociągu:

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

I można go używać tak:

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) 

i conv będzie mieć oczekiwany typ statyczny (i implementować Conversion[AutoA, AutoC]).