2014-10-28 12 views
6

Próbuję grać z HList z Shapeless.Co zwraca funkcja HList # foldLeft()?

To moja pierwsza próba:

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 
} 

Problem polega na tym, że nie wiem, co foldLeft ma powrócić w tym przykładzie.

Oczekuję, że zwróci (String, String, String), ale kompilator mówi mi, że zwraca l.Out. Co to jest l.Out?

Kod źródłowy jest nieco trudny do odgadnięcia.

W sieci nie ma zbyt wielu informacji na ten temat.

Niektóre informacje mam konsultowany:

Odpowiedz

11

Twoja metoda combine powraca co nazywa się "dependent method type", co oznacza po prostu, że jego typ zwracany zależy od jednego z jej argumentów - w tym przypadku jako typ zależny od ścieżki, który obejmuje ścieżkę l.

W wielu przypadkach kompilator będzie statycznie wiedział coś na temat zależnego typu zwracanego, ale w twoim przykładzie tak nie jest. Postaram się wyjaśnić, dlaczego w drugim, ale najpierw rozważyć następujące prostszy przykład:

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 

Tutaj wywnioskować typ res0 jest String, ponieważ kompilator statycznie wie, że A z foo argumentem jest String. Nie możemy napisać jedną z następujących czynności, choć:

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 
           ^

Bo tu kompilator nie statycznie, że foo.A jest String.

Oto bardziej skomplikowany przykład:

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

Teraz wiemy, że nie jest możliwe stworzenie Baz z różnymi rodzajami dla A i B, ale kompilator nie robi, więc nie będzie zaakceptuj następujące:

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 
                  ^

To jest dokładnie to, co widzisz. Jeśli spojrzymy na kod w shapeless.ops.hlist, możemy przekonać się, że tworzone tutaj LeftFolder będzie mieć ten sam typ dla In i Out, ale kompilator nie może (lub raczej nie będzie -to decyzja projektowa) podążaj za nami w tym rozumowaniu, co oznacza, że ​​nie pozwala nam traktować l.Out jako krotki bez dodatkowych dowodów.

szczęście, że dowody są dość łatwe do zapewnienia dzięki LeftFolder.Aux, który jest po prostu aliasem LeftFolder z członem Out typu jako czwarty parametr typu:

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 

(można też użyć składni członek typ ze zwykłego starego LeftFolder w rodzaju l „s, ale to by uczynić ten podpis nawet Messier.)

columns.foldLeft(...)(...) część nadal zwraca l.Out, ale teraz kompilator statycznie wie, że to jest krotka o f struny.

+2

Wyjaśniłeś skomplikowaną koncepcję w łatwy do zrozumienia sposób. Gratulacje!! –

+0

Bardzo dobra odpowiedź Travis :) Chciałbym, żeby dokumenty były bardziej podobne do tego: – ahjohannessen

+0

Co zrobić, jeśli 'foldLeft()' zwraca krotkę zawierającą HList ?. Kompiluje się, ale kiedy próbuję go użyć, kompilator narzeka na implicite. Jeśli to konieczne, mogę utworzyć kolejne pytanie. –

0

Po zapoznania się z pełną odpowiedź od Travisa, tutaj jest trochę zmienność jego rozwiązanie:

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 

W ten sposób, podpis niejawna jest krótszy, jak to jest potrzebne w wielu metod, które nazywają ten jeden .

ZAKTUALIZOWANY: Jak wyjaśnił Travis w komentarzach, najlepiej jest użyć LeftFolder.Aux.

+2

Nie zostanie to jednak skompilowane, ponieważ 'LeftFolder.Aux' potrzebuje czterech parametrów typu. Obsada z 'asInstanceOf' również sprawia, że ​​kod jest kruchy - po rzutowaniu rezygnujesz z bezpieczeństwa typu, np. kompilator może nie być w stanie powiedzieć, jeśli dokonasz zmiany, która coś zepsuje. –

+0

Poprawiono: LeftFolder.Aux -> LeftFolder. –

+0

Używając 'LeftFolder.Aux', a ja określam zły typ zwracania, czy kompilator je wykrywa ?. –