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.
Wyjaśniłeś skomplikowaną koncepcję w łatwy do zrozumienia sposób. Gratulacje!! –
Bardzo dobra odpowiedź Travis :) Chciałbym, żeby dokumenty były bardziej podobne do tego: – ahjohannessen
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. –