您好:我最近一直在学习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
将绑定到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))
}
从本质上讲,我的问题是:为什么这有效? ;(我希望这两个函数具有相同的整体类型签名:
1: [Input <:HList, Output <:HList] (Int::Input)(Object):Int::Output
2: [Input <:Hlist, Output <:HList] :Int::Input=>Object=>Int::Output
如果它有助于理解问题,其他一些变化也“起作用”(虽然这些变化会改变函数的语义,因此不是可用的解决方案):
3)通过将输出替换为HNil来强制编码runAll
仅用于第二级
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
不能很好地合作”对我来说没有意义。)
答案 0 :(得分:6)
当您声明implicit def
时,这样:
implicit def makeStr(i: Int): String = i.toString
然后编译器可以自动为此定义创建隐式Function
对象,并将其插入到需要隐式类型Int => String
的位置。这就是您的行HList.runAny(HNil())(null)
中发生的情况。
但是当你定义自己接受隐式参数的implicit def
时(比如你的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))
}
此定义更加明确,因为编译器现在不需要尝试从Function
创建def
对象,而是调用 def
直接获取所需的函数对象。并且,在调用它时,将自动插入所需的隐式参数,它可以立即解决。
在我看来,每当你期望这种类型的隐式函数时,你应该提供一个确实返回implicit def
对象的Function
。 (其他用户可能不同意......任何人?)编译器能够围绕Function
创建implicit def
包装器这一事实主要是我支持使用更自然的语法支持隐式转换,例如将view bounds与简单的implicit def
一起使用,就像我的第一个Int
转换为String
一样。
答案 1 :(得分:3)
(注意:这是对此问题的另一个答案的评论部分中可能更详细的讨论摘要。)
事实证明,这里的问题是 implicit
参数不是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
,其中Input
和Output
实际上是同一类型),它只是绑定到conforms:<:<[Input,Output]
:一个函数反过来行动作为这种情况下的身份。
(有关#4案例的更多信息,请参阅此明显与死相关的问题:problem with implicit ambiguity between my method and conforms in Predef。)