两个看似相同的语义:一个隐式绑定,另一个不绑定

Jay*_*ik- 3 scala implicits

您好:我最近一直在学习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 :(
    }
}
Run Code Online (Sandbox Code Playgroud)

使用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)
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我的期望是runAll与隐含的run参数绑定runAny.

现在,如果我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))
    }
Run Code Online (Sandbox Code Playgroud)

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

1: [Input <:HList, Output <:HList] (Int::Input)(Object):Int::Output
2: [Input <:Hlist, Output <:HList] :Int::Input=>Object=>Int::Output
Run Code Online (Sandbox Code Playgroud)

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

3)runAll通过用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))
    }
Run Code Online (Sandbox Code Playgroud)

4)从隐式函数中删除context参数

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

任何人可能对此有任何解释将非常感激.:(

(目前,我最好的猜测是隐式参数相对于其他参数的顺序是我缺少的关键因素,但是我很困惑的一个:runAny在最后也有一个隐含的参数,所以显而易见的" implicit def尾随效果不好implicit"对我来说没有意义.)

Jea*_*let 6

当你声明implicit def这样的时候:

implicit def makeStr(i: Int): String = i.toString
Run Code Online (Sandbox Code Playgroud)

然后编译器可以自动Function为此定义创建一个隐式对象,并将其插入到需要隐式类型的Int => String位置.这是你的行中发生的事情HList.runAny(HNil())(null).

但是当你定义implicit def自己接受隐式参数的s(比如你的runAll方法)时,它就不再起作用,因为编译器不能创建一个方法需要隐式的Function对象apply- 更不用说保证这样的隐式参数可以在呼叫网站.

对此的解决方案是定义类似这样的内容而不是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))
  }
Run Code Online (Sandbox Code Playgroud)

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

在我看来,无论何时你期望这种类型的隐式函数,你应该提供一个implicit def确实返回一个Function对象.(其他用户可能不同意......人?)编译器能够创建事实上Function包装围绕一个implicit def有主要的,我想,以支持隐式转换具有更自然的语法,例如使用视图边界简单的在一起implicit def就像我第一次IntString转换.