问题描述
||
您好:我最近一直在学习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)
在这种情况下,我的期望是将“ 2”绑定到“ 4”的隐式“ 3”自变量。
现在,如果我修改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)通过将Output替换为HNil,仅对第二层进行ѭ2硬编码
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
与尾随ѭ13不能很好地工作”对我来说没有意义。)
解决方法
当您这样声明
implicit def
时:
implicit def makeStr(i: Int): String = i.toString
那么编译器可以根据您的定义自动为您创建一个隐式Function
对象,并将其插入期望为Int => String
类型的隐式对象。这就是您的行HList.runAny(HNil())(null)
中发生的情况。
但是,当您定义本身接受隐式参数的implicit def
(例如您的runAll
方法)时,它将不再起作用,因为编译器无法创建其apply
方法将需要隐式的Function
对象,而更少地保证了此类隐式将在呼叫站点可用。
解决方案是定义类似这样的内容,而不是of2ѭ:
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))
}
这个定义更加明确,因为编译器现在不需要尝试从ѭ26中创建一个a16ѭ对象,而是直接调用def
来获取所需的函数对象。并且,在调用它时,将自动插入所需的隐式参数,该参数可以立即解析。
我认为,每当您期望这种隐式函数时,都应提供一个确实返回a16ѭ对象的implicit def
。 (其他用户可能会不同意……有人吗?)我想,编译器能够在implicit def
周围创建Function
包装器这一事实主要是为了支持使用更自然的语法(例如)进行隐式转换。将视图边界与简单的ѭ12一起使用,例如我第一次将ѭ33转换为String
。
,(注意:这是对该问题的另一个答案的注释部分中可能更详细地进行的讨论的摘要。)
事实证明,这里的问题是implicit
参数不是runAny
中的第一个,而是不是因为隐式绑定机制忽略了它:相反,问题在于类型参数Output
没有绑定任何东西,需要间接地从“ 3”隐式参数的类型推断出这种情况,发生时间“太晚”。
本质上,“不确定类型参数”的代码(在这种情况下为is37)仅在以下情况下使用:所讨论的方法被认为是“隐式”,由其直接参数确定list:在这种情况下,runAny
的参数列表实际上只是(input :Input)
,不是“隐式”。
因此,Input
的类型参数设法起作用(设置为Int::HNil
),但Output
只是设置为Nothing
,“粘住”并使causes3ѭ参数的类型为Int::HNil=>Object=>Nothing
,runNil
无法满足,导致runAny
的类型推断失败,从而取消了其资格用作runAll
的隐式参数。
通过按照修改后的代码示例2的方式重新组织参数,我们使runAny
本身为“隐式”,从而使其在应用其余参数之前先完全确定其类型参数:发生这种情况是因为其隐式参数将首先获得绑定到runNil
(或再次runAny
超过两个级别),其返回类型将被采用/绑定。
束缚松散的结局:代码示例3在这种情况下起作用的原因是甚至不需要Output
参数:将其绑定到Nothing
的事实并不影响随后的任何绑定它的尝试。到任何东西或将其用于任何东西,并且容易选择runNil
绑定到其版本的run
隐式参数。
最后,代码示例4实际上是退化的,甚至不应该被认为是“工作”(我只验证了它已编译,而不是它生成了适当的输出):隐式参数的数据类型太简单了(Input=>Output
,其中Input
和Output
实际上是同一类型),以至于它会简单地绑定到a61 function:在这种情况下,该函数又充当了身份。
(有关第4个案例的更多信息,请参见这个显然与时俱进的问题:我的方法与在Predef中符合的隐式歧义问题。)