2014-10-28 53 views
6

我正在嘗試與無形狀的HList玩。HList#foldLeft()返回什麼?

這是我第一次嘗試:

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 
} 

問題是我不知道是什麼foldLeft在這個例子並返回。

我希望它返回(String, String, String),但編譯器告訴我返回l.Out。什麼是l.Out

源代碼有點複雜的猜測它。

網絡上沒有太多關於此的信息。

一些信息我請教:

回答

11

combine方法返回什麼叫做"dependent method type",這只是意味着它的返回類型取決於它的一個參數 - 在這種情況下,作爲路徑依賴類型,其路徑中包含l

在很多情況下,編譯器會靜態地知道一些關於依賴返回類型的內容,但在你的例子中它不會。我會盡力解釋爲什麼在第二,但首先考慮以下簡單的例子:

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 

這裏推斷出的類型的res0String,因爲編譯器靜態地知道,foo參數的AString。我們可以不寫或者以下的,雖然:

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 
           ^

因爲這裏的編譯器不靜態地知道foo.AString

下面是一個更復雜的例子:

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

現在我們知道這是不可能的創建具有不同類型AB一個Baz,但是編譯器沒有,所以不會接受以下內容:

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 
                  ^

這正是您所看到的。如果我們看shapeless.ops.hlist中的代碼,我們可以說服我們自己在這裏創建的LeftFolder將具有與InOut相同的類型,但編譯器不能(或者更確切地說不會-這是一個設計決定)在這個推理中跟隨我們,這意味着它不會讓我們把l.Out當作一個元組而不需要更多的證據。

幸運的證據是很容易提供拜LeftFolder.Aux,這僅僅是一個與Out型成員作爲第四類參數LeftFolder別名:

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 

(你也可以使用該類型的成員語法與普通的舊LeftFolderl的類型,但是這將讓這個簽名甚至混亂。)

columns.foldLeft(...)(...)部分仍返回l.Out,但現在的編譯器靜態地知道,這是一個元組Ø f字符串。

+2

你已經用簡單易懂的方式解釋了一個複雜的概念。恭喜!! – 2014-10-28 14:33:37

+0

非常好的回答Travis :)我希望文檔更像這樣 – ahjohannessen 2014-10-28 20:38:39

+0

如果'foldLeft()'返回一個包含HList的元組呢?它編譯,但是當我嘗試使用它時,編譯器會抱怨implicits。如有必要,我可以創建另一個問題。 – 2014-10-29 13:01:14

0

看了特拉維斯完整的答案後,這裏是他的解決方案的一個小的變化:

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 

這樣,隱含的簽名是短的,因爲它需要在調用這個方法很多。

已更新:正如Travis在評論中解釋的那樣,最好使用LeftFolder.Aux

+2

雖然這不會編譯,因爲'LeftFolder.Aux'需要四個類型參數。帶有'asInstanceOf'的轉換也會使代碼變得脆弱 - 當你投射時你會放棄類型安全,例如編譯器可能無法告訴你,如果你做了一些改變,會破壞某些東西。 – 2014-10-29 11:33:30

+0

已更正:LeftFolder.Aux - > LeftFolder。 – 2014-10-29 11:50:27

+0

當使用'LeftFolder.Aux'時,我指定了一個錯誤的返回類型,編譯器是否檢測到它? – 2014-10-29 11:51:46