2014-10-28 8 views
6

Ich versuche mit HLists von Shapeless zu spielen.Was gibt HList # foldLeft() zurück?

Dies ist mein erster Versuch:

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 
} 

Das Problem ist, ich weiß nicht, was foldLeft in diesem Beispiel zurückgibt.

Ich erwarte, dass es (String, String, String) zurückgibt, aber der Compiler sagt mir, dass l.Out zurückgibt. Was ist l.Out?

Der Quellcode ist ein wenig kompliziert zu erraten.

Es gibt nicht viele Informationen im Internet darüber.

Einige Informationen, die ich konsultiert habe:

Antwort

11

Ihre combine Methode gibt, was ein "dependent method type" genannt, das bedeutet nur, dass deren Rückgabetyp auf einer seiner Argumente hängt - In diesem Fall als pfadabhängiger Typ, der l in seinem Pfad enthält.

In vielen Fällen wird der Compiler statisch etwas über den abhängigen Rückgabetyp wissen, aber in Ihrem Beispiel nicht. Ich werde versuchen, warum in einem zweiten zu erklären, aber zunächst die nachfolgend aufgeführten einfacheres Beispiel betrachten:

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 

Hier die abgeleitete Art von res0 ist String, da statisch der Compiler weiß, dass die A des foo Argument ist String. Wir können nicht eine der folgenden schreiben, aber:

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 
           ^

Da hier der Compiler nicht weiß, statisch, dass foo.AString ist.

Hier ist ein komplexeres Beispiel:

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

Jetzt wir wissen, dass es nicht möglich ist, ein Baz mit verschiedenen Typen für A und B, aber der Compiler nicht, zu schaffen, so wird es nicht Akzeptieren Sie Folgendes:

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 
                  ^

Dies ist genau das, was Sie sehen. Wenn wir uns den Code in shapeless.ops.hlist ansehen, können wir uns davon überzeugen, dass die LeftFolder, die wir hier erstellen, den gleichen Typ für In und Out haben wird, aber der Compiler kann nicht (wird nicht -Es ist eine Designentscheidung) Folgen Sie uns in dieser Argumentation, was bedeutet, dass wir l.Out nicht als ein Tupel ohne weitere Beweise behandeln werden.

Glück, dass die Beweise sind ziemlich einfach, dank LeftFolder.Aux zu schaffen, die für LeftFolder mit dem Out Typ Elemente als ein vierten Typ Parameter nur ein Alias ​​ist:

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 

(Sie auch die Art Mitglied Syntax verwenden könnten mit plain old LeftFolder in l ‚s Art, aber das würde diese Signatur auch chaotischer machen.)

der columns.foldLeft(...)(...) Teil l.Out noch zurück, aber jetzt ist der Compiler weiß, statisch, daß das ein Tupel o f Saiten.

+2

Sie haben ein komplexes Konzept auf leicht verständliche Weise erklärt. Glückwunsch!! –

+0

Sehr nette Antwort Travis :) Ich wünschte, die Dokumente wären mehr wie das – ahjohannessen

+0

Was ist, wenn 'foldLeft()' ein Tupel zurückgibt, das ein HList enthält ?. Es kompiliert, aber wenn ich versuche, es zu verwenden, beschwert sich der Compiler über implicits. Wenn nötig, kann ich eine andere Frage stellen. –

0

Nachdem die vollständige Antwort von Travis gelesen zu haben, hier ein wenig Variation seiner Lösung ist:

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 

Auf diese Weise ist die implizite Unterschrift kürzer, da es in vielen Methoden, die diesen einen Anruf benötigt wird .

AKTUALISIERT: Wie Travis in Kommentaren erklärt hat, ist es am besten zu verwenden LeftFolder.Aux.

+2

Dies wird jedoch nicht kompiliert, da 'LeftFolder.Aux' vier Typparameter benötigt. Die Besetzung mit 'asInstanceOf' macht den Code auch fragil - du gibst die Sicherheit beim Tippen auf, also z. Der Compiler kann Ihnen möglicherweise nicht sagen, ob Sie eine Änderung vornehmen, die etwas kaputt macht. –

+0

Korrigiert: LeftFolder.Aux -> LeftFolder. –

+0

Wenn 'LeftFolder.Aux' verwendet wird und ich einen falschen Rückgabetyp angegeben habe, erkennt der Compiler dies ?. –