3

你好:我最近一直在学习 Scala(我的相关背景主要是 C++ 模板),我遇到了一些我目前对 Scala 不了解的东西,这让我发疯了。:(

(另外,这是我在 StackOverflow 上的第一篇文章,我注意到大多数非常棒的 Scala 人似乎都在闲逛,所以如果我对这个机制做了一些非常愚蠢的事情,我真的很抱歉。)

我的具体困惑与隐式参数绑定有关:我提出了一个特定情况,即隐式参数拒绝绑定,但具有看似相同语义的函数却可以。

现在,它当然可能是一个编译器错误,但鉴于我刚刚开始使用 Scala,我已经遇到某种严重错误的可能性非常小,以至于我期待有人解释我做错了什么。;P

我已经浏览了代码并对其进行了相当多的削减,以便提出一个不起作用的示例。不幸的是,这个例子仍然相当复杂,因为这个问题似乎只出现在泛化中。:(

1) 不按我预期的方式工作的简化代码

import HList.::

trait HApplyOps {
    implicit def runNil
        (input :HNil)
        (context :Object)
        :HNil
    = {
        HNil()
    }

    implicit def runAll[Input <:HList, Output <:HList]
        (input :Int::Input)
        (context :Object)
        (implicit run :Input=>Object=>Output)
        :Int::Output
    = {
        HCons(0, run(input.tail)(context))
    }

    def runAny[Input <:HList, Output <:HList]
        (input :Input)
        (context :Object)
        (implicit run :Input=>Object=>Output)
        :Output
    = {
        run(input)(context)
    }
}

sealed trait HList

final case class HCons[Head, Tail <:HList]
    (head :Head, tail :Tail)
    extends HList
{
    def ::[Value](value :Value) = HCons(value, this)
}

final case class HNil()
    extends HList
{
    def ::[Value](value :Value) = HCons(value, this)
}

object HList extends HApplyOps {
    type ::[Head, Tail <:HList] = HCons[Head, Tail]
}

class Test {
    def main(args :Array[String]) {
        HList.runAny(   HNil())(null) // yay! ;P
        HList.runAny(0::HNil())(null) // fail :(
    }
}

此代码使用 Scala 2.9.0.1 编译,返回以下错误:

broken1.scala:53: error: No implicit view available from HCons[Int,HNil] => (java.lang.Object) => Output.
        HList.runAny(0::HNil())(null)

在这种情况下,我的期望runAll是绑定runrunAny.

现在,如果我修改runAll它,而不是直接获取它的两个参数,而是返回一个函数,该函数又接受这两个参数(我想尝试的技巧,因为我在其他人的代码中看到它),它可以工作:

2) 修改后的代码具有相同的运行时行为并且实际工作

    implicit def runAll[Input <:HList, Output <:HList]
        (implicit run :Input=>Object=>Output)
        :Int::Input=>Object=>Int::Output
    = {
        input =>
        context =>
        HCons(0, run(input.tail)(context))
    }

从本质上讲,我的问题是:为什么会这样?;( 我希望这两个函数具有相同的整体类型签名:

1: [Input <:HList, Output <:HList] (Int::Input)(Object):Int::Output
2: [Input <:Hlist, Output <:HList] :Int::Input=>Object=>Int::Output

如果它有助于理解问题,其他一些更改也“起作用”(尽管这些更改了函数的语义,因此不是可用的解决方案):

3)runAll通过将 Output 替换为 HNil 仅对第二级进行硬编码

    implicit def runAll[Input <:HList, Output <:HList]
        (input :Int::Input)
        (context :Object)
        (implicit run :Input=>Object=>HNil)
        :Int::HNil
    = {
        HCons(0, run(input.tail)(context))
    }

4) 从隐式函数中删除上下文参数

trait HApplyOps {
    implicit def runNil
        (input :HNil)
        :HNil   
    = {
        HNil()  
    }

    implicit def runAll[Input <:HList, Output <:HList]
        (input :Int::Input)
        (implicit run :Input=>Output)
        :Int::Output
    = {
        HCons(0, run(input.tail))
    }

    def runAny[Input <:HList, Output <:HList]
        (input :Input) 
        (context :Object)
        (implicit run :Input=>Output)
        :Output 
    = {
        run(input)
    }
}

任何人对此可能有的任何解释将不胜感激。:(

(目前,我最好的猜测是隐式参数相对于其他参数的顺序是我遗漏的关键因素,但我感到困惑的是:runAny最后也有一个隐式参数,所以明显的“implicit def不能很好地与尾随implicit”对我来说没有意义。)

4

2 回答 2

6

当你声明implicit def这样的:

implicit def makeStr(i: Int): String = i.toString

然后编译器可以根据这个定义自动为你创建一个隐式Function对象,并将它插入到需要隐式类型Int => String的地方。这就是在您的线路中发生的事情HList.runAny(HNil())(null)

但是,当您定义implicit def自己接受隐式参数的 s (如您的runAll方法)时,它不再起作用,因为编译器无法创建Functionapply方法需要隐式的对象——更不用说保证这样的隐式在呼叫站点。

解决方案是定义类似这样的东西,而不是runAll

implicit def runAllFct[Input <: HList, Output <: HList]
    (implicit run: Input => Object => Output):
        Int :: Input => Object => Int :: Output =
  { input: Int :: Input =>
    context: Object =>
      HCons(0, run(input.tail)(context))
  }

这个定义更明确一点,因为编译器现在不需要尝试Function从你的创建对象def,而是直接调用def来获取所需的函数对象。并且,在调用它时,会自动插入所需的隐式参数,它可以立即解析。

在我看来,每当你期望这种类型的隐式函数时,你应该提供一个implicit def确实返回一个Function对象的函数。(其他用户可能不同意……有人吗?)我想,编译器能够Function围绕 an 创建包装器的事实implicit def主要是为了支持具有更自然语法的隐式转换,例如将视图边界与简单implicit def的 s 一起使用,就像我的第Int一个String转换。

于 2011-05-30T09:43:57.723 回答
3

(注意:这是对该问题的另一个答案的评论部分中可能更详细地进行的讨论的摘要。)

事实证明,这里的问题是参数implicit不是 first in runAny,而不是因为隐式绑定机制忽略了它:相反,问题是类型参数Output没有绑定到任何东西,需要从隐式参数的类型,run“为时已晚”。

本质上,“未确定类型参数”的代码(Output在这种情况下就是这样)仅在所讨论的方法被认为是“隐式”的情况下使用,这由其直接参数列表确定:在这种情况下,runAny的参数列表实际上只是 (input :Input),而不是“隐式”。

因此, 的类型参数Input设法工作(设置为Int::HNil),但Output只是设置为Nothing,它“粘住”并导致run参数的类型为Int::HNil=>Object=>Nothing,这不能满足runNil,导致runAny的类型推断失败,取消资格它用作runAll.

通过像我修改后的代码示例 #2 中所做的那样重新组织参数,我们使runAny自己成为“隐式”,允许它在应用其剩余参数之前首先完全确定其类型参数:这是因为它的隐式参数将首先绑定到runNil(或runAny再次超过两个级别),其返回类型将被采用/绑定。

为了解决松散的问题:代码示例#3在这种情况下工作的原因是该Output参数甚至不是必需的:它被绑定的事实Nothing并不影响任何后续尝试将其绑定到任何东西或使用它适用于任何东西,并且runNil很容易选择绑定到其run隐式参数的版本。

最后,代码示例 #4 实际上是退化的,甚至不应该被认为是“工作”(我只是验证了它是否已编译,而不是它生成了适当的输出):它的隐式参数的数据类型是如此简单( Input=>Output, where InputandOutput实际上是同一类型),它只会被绑定到conforms:<:<[Input,Output]: 在这种情况下又充当身份的函数。

(有关 #4 案例的更多信息,请参阅这个明显死气沉沉的相关问题:problem with implicit ambiguity between my method and conforms in Predef。)

于 2011-05-31T11:08:31.090 回答