【问题标题】:What does HList#foldLeft() return?HList#foldLeft() 返回什么?
【发布时间】:2014-10-29 21:29:57
【问题描述】:

我正在尝试使用 Shapeless 中的 HList。

这是我的第一次尝试:

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.Outl.Out是什么?

源码猜起来有点复杂。

网络上没有太多关于此的信息。

我查阅过的一些资料:

【问题讨论】:

    标签: scala shapeless


    【解决方案1】:

    您的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
    

    这里res0 的推断类型是String,因为编译器静态知道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,提供证据非常容易,它只是LeftFolder 的别名,Out 类型成员作为第四个类型参数:

    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
    

    (您也可以在 l 的类型中使用带有普通旧 LeftFolder 的类型成员语法,但这会使此签名更加混乱。)

    columns.foldLeft(...)(...) 部分仍然返回 l.Out,但现在编译器静态知道这是一个字符串元组。

    【讨论】:

    • 您以易于理解的方式解释了一个复杂的概念。恭喜!!
    • 很好的答案特拉维斯 :) 我希望文档更像这样
    • 如果foldLeft() 返回一个包含 HList 的元组呢?它可以编译,但是当我尝试使用它时,编译器会抱怨隐式。如有必要,我可以创建另一个问题。
    • 您可以添加隐式参数来提供有关类型的证据。不过,可能值得另一个问题。
    【解决方案2】:

    在阅读了 Travis 的完整答案后,以下是他的解决方案的一些变化:

    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 在 cmets 中解释的那样,最好使用 LeftFolder.Aux

    【讨论】:

    • 这不会编译,因为LeftFolder.Aux 需要四个类型参数。 asInstanceOf 的强制转换也会使代码变得脆弱——你在强制转换时放弃了类型安全,例如编译器可能无法告诉您是否进行了破坏某些内容的更改。
    • 更正:LeftFolder.Aux --> LeftFolder.
    • 使用LeftFolder.Aux时,我指定了错误的返回类型,编译器会检测到吗?
    • 不,asInstanceOf 是直接转换,所以你可以输入任何你喜欢的类型,直到运行时你才知道它是否有效。
    猜你喜欢
    • 2021-02-17
    • 2016-11-29
    • 1970-01-01
    • 1970-01-01
    • 2017-04-22
    • 2021-02-18
    • 1970-01-01
    • 2017-03-25
    • 1970-01-01
    相关资源
    最近更新 更多