2014-10-28 15 views
6

Sto provando a giocare con HList di Shapeless.Cosa restituisce HList # foldLeft()?

Questo è il mio primo tentativo:

trait Column[T] { 
    val name: String 
} 

case class CV[T](col: Column[T], value: T) 

object CV { 
    object columnCombinator extends Poly2 { 
     implicit def algo[A] = at[(String, String, String), CV[A]] { case ((suffix, separator, sql), cv) ⇒ 
      (suffix, separator, if (sql == "") cv.col.name+suffix else sql+separator+cv.col.name+suffix) 
     } 
    } 

    def combine[A <: HList](columns: A, suffix: String, separator: String = " and ") 
          (implicit l: LeftFolder[A, (String, String, String), columnCombinator.type]): String = 
     columns.foldLeft((suffix, separator, ""))(columnCombinator)._3 
} 

Il problema è che non so cosa foldLeft fa ritorno in questo esempio.

Mi aspetto che restituisca (String, String, String), ma il compilatore mi dice che restituisce l.Out. Che cos'è l.Out?

Il codice sorgente è un po 'complicato da indovinare.

Non ci sono molte informazioni nel web su questo.

Alcune informazioni che ho consultato:

risposta

11

Il tuo metodo combine restituisce quello che è chiamato un "dependent method type", il che significa semplicemente che il suo tipo di ritorno dipende da uno dei suoi argomenti -in questo caso come tipo dipendente dal percorso che include l nel suo percorso.

In molti casi il compilatore conoscerà staticamente qualcosa sul tipo di ritorno dipendente, ma nel tuo esempio non lo fa. Cercherò di spiegare perché in un secondo, ma prima si consideri il seguente esempio più semplice:

scala> trait Foo { type A; def a: A } 
defined trait Foo 

scala> def fooA(foo: Foo): foo.A = foo.a 
fooA: (foo: Foo)foo.A 

scala> fooA(new Foo { type A = String; def a = "I'm a StringFoo" }) 
res0: String = I'm a StringFoo 

Qui il tipo derivato di res0 è String, dal momento che il compilatore sa staticamente che il A dell'argomento foo è String. Non possiamo scrivere una delle seguenti, però:

scala> def fooA(foo: Foo): String = foo.a 
<console>:12: error: type mismatch; 
found : foo.A 
required: String 
     def fooA(foo: Foo): String = foo.a 
             ^

scala> def fooA(foo: Foo) = foo.a.substring 
<console>:12: error: value substring is not a member of foo.A 
     def fooA(foo: Foo) = foo.a.substring 
           ^

Perché qui il compilatore non sa staticamente che foo.A è String.

Ecco un esempio più complesso:

sealed trait Baz { 
    type A 
    type B 

    def b: B 
} 

object Baz { 
    def makeBaz[T](t: T): Baz { type A = T; type B = T } = new Baz { 
    type A = T 
    type B = T 

    def b = t 
    } 
} 

Ora abbiamo sappiamo che non è possibile creare un Baz con diversi tipi di A e B, ma il compilatore non lo fa, in modo da non accetta quanto segue:

scala> def bazB(baz: Baz { type A = String }): String = baz.b 
<console>:13: error: type mismatch; 
found : baz.B 
required: String 
     def bazB(baz: Baz { type A = String }): String = baz.b 
                  ^

Questo è esattamente quello che stai vedendo. Se guardiamo il codice in shapeless.ops.hlist, possiamo convincerci che lo LeftFolder che stiamo creando qui avrà lo stesso tipo per In e Out, ma il compilatore non può (o meglio non sarà -è una decisione di progettazione) seguici in questo ragionamento, il che significa che non ci consentirà di trattare l.Out come una tupla senza ulteriori prove.

Per fortuna che la prova è abbastanza facile da fornire grazie alla LeftFolder.Aux, che è solo un alias per LeftFolder con il membro Out caratteristiche di un quarto parametro tipo:

def combine[A <: HList](columns: A, suffix: String, separator: String = " and ")(
    implicit l: LeftFolder.Aux[ 
    A, 
    (String, String, String), 
    columnCombinator.type, 
    (String, String, String) 
    ] 
): String = 
    columns.foldLeft((suffix, separator, ""))(columnCombinator)._3 

(Si potrebbe anche usare la sintassi membro tipo con pianura vecchio LeftFolder nel tipo 's l, ma che avrebbe fatto questa firma anche Messier.)

la parte columns.foldLeft(...)(...) restituisce ancora l.Out, ma ora il compilatore sa staticamente che questa è una tupla o stringhe.

+2

Hai spiegato un concetto complesso in un modo facile da capire. Congratulazioni!! –

+0

Risposta molto piacevole Travis :) Vorrei che i documenti fossero più come questo – ahjohannessen

+0

E se 'foldLeft()' restituisce una tupla che contiene una lista H ?. Compilare, ma quando provo ad usarlo, il compilatore si lamenta degli impliciti. Se necessario, posso creare un'altra domanda. –

0

Dopo aver letto la risposta completa da Travis, ecco un po 'di variazione della sua soluzione:

type CombineTuple = (String, String, String) 

def combine[A <: HList](columns: A, suffix: String, separator: String = " and ")(
    implicit l: LeftFolder[ 
    A, 
    CombineTuple, 
    columnCombinator.type 
    ] 
): String = 
    columns.foldLeft((suffix, separator, ""))(columnCombinator).asInstanceof[CombineTuple]._3 

In questo modo, la firma implicita è più breve, in quanto è necessario in molti metodi che chiamano questo uno .

AGGIORNATO: Come ha spiegato Travis nei commenti, è meglio usare LeftFolder.Aux.

+2

Questo non verrà compilato, poiché 'LeftFolder.Aux' richiede quattro parametri di tipo. Il cast con 'asInstanceOf' rende anche il codice fragile: stai rinunciando al tipo di sicurezza quando lanci, quindi ad es. il compilatore potrebbe non essere in grado di dirti se apporti una modifica che rompe qualcosa. –

+0

corretto: LeftFolder.Aux -> LeftFolder. –

+0

Quando si utilizza 'LeftFolder.Aux' e si specifica un tipo di ritorno errato, il compilatore lo rileva ?. –