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

发布于 2024-11-10 07:12:36 字数 3761 浏览 8 评论 0原文

你好:我最近在学习Scala(我的相关背景主要是C++模板),我遇到了一些我目前对Scala不太了解的地方,这让我抓狂。 :(

(另外,这是我在 StackOverflow 上发表的第一篇文章,我注意到大多数非常棒的 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 将绑定到 runAny 的隐式 run 参数。

现在,如果我修改 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 does not work well with Trailing implicit”对我来说没有意义。)

Hello: I've been learning Scala recently (my related background is mostly in C++ templates), and I've run into something I currently don't understand about Scala, and it is driving me insane. :(

(Also, this is my first post to StackOverflow, where I've noticed most of the really awesome Scala people seem to hang out, so I'm really sorry if I do something horrendously stupid with the mechanism.)

My specific confusion relates to implicit argument binding: I have come up with a specific case where the implicit argument refuses to bind, but a function with seemingly identical semantics does.

Now, it of course could be a compiler bug, but given that I just started working with Scala, the probability of me having already run into some kind of serious bug are sufficiently small that I'm expecting someone to explain what I did wrong. ;P

I have gone through the code and whittled it quite a bit in order to come up with the single example that doesn't work. Unfortunately, that example is still reasonably complex, as the problem seems to only occur in the generalization. :(

1) simplified code that does not work in the way I expected

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 :(
    }
}

This code, compiled with Scala 2.9.0.1, returns the following error:

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

My expectation in this case is that runAll would be bound to the implicit run argument to runAny.

Now, if I modify runAll so that, instead of taking its two arguments directly, it instead returns a function that in turn takes those two arguments (a trick I thought to try as I saw it in someone else's code), it works:

2) modified code that has the same runtime behavior and actually works

    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))
    }

In essence, my question is: why does this work? ;( I would expect that these two functions have the same overall type signature:

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

If it helps understand the problem, some other changes also "work" (although these change the semantics of the function, and therefore are not usable solutions):

3) hard-coding runAll for only a second level by replacing Output with 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) removing the context argument from the implicit functions

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)
    }
}

Any explanation anyone may have for this would be much appreciated. :(

(Currently, my best guess is that the order of the implicit argument with respect to the other arguments is the key factor that I'm missing, but one that I'm confused by: runAny has an implicit argument at the end as well, so the obvious "implicit def doesn't work well with trailing implicit" doesn't make sense to me.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

┾廆蒐ゝ 2024-11-17 07:12:36

当您像这样声明一个隐式def时:

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

那么编译器可以根据该定义自动为您创建一个隐式Function对象,并将其插入到类型的隐式对象中>整数=>需要字符串。这就是您的行 HList.runAny(HNil())(null) 中发生的情况。

但是,当您定义本身接受隐式参数的 implicit def 时(例如您的 runAll 方法),它就不再起作用,因为编译器无法创建 apply 方法需要隐式的 Function 对象,更不用说保证这样的隐式在调用站点可用。

解决这个问题的方法是定义类似这样的东西,而不是 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 来获取所需的函数对象。并且,在调用它时,将自动插入所需的隐式参数,它可以立即解析该参数。

在我看来,每当您期望这种类型的隐式函数时,您都应该提供一个确实返回 Function 对象的 implicit def 。 (其他用户可能不同意......任何人?)事实上,编译器能够围绕 implicit def 创建 Function 包装器,我想主要是为了支持隐式转换更自然的语法,例如使用 view bounds 与简单的隐式 def 一起使用,就像我的第一个 IntString 转换。

When you declare an implicit def like this:

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

then the compiler can automatically create an implicit Function object from this definition for you, and will insert it where an implicit of type Int => String is expected. This is what happens in your line HList.runAny(HNil())(null).

But when you define implicit defs which themselves accept implicit parameters (like your runAll method), it doesn't work any more, as the compiler cannot create a Function object whose apply method would require an implicit — much less guarantee that such an implicit would be available at the call site.

The solution to this is to define something like this instead of 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))
  }

This definition is a bit more explicit, as the compiler now won't need to try to create a Function object from your def, but will instead call your def directly to get the needed function object. And, while calling it, will automatically insert the needed implicit parameter, which it can resolve right away.

In my opinion, whenever you expect implicit functions of this type, you should provide an implicit def that does indeed return a Function object. (Other users may disagree… anyone?) The fact that the compiler is able to create Function wrappers around an implicit def is there mainly, I suppose, to support implicit conversions with a more natural syntax, e.g. using view bounds together with simple implicit defs like my first Int to String conversion.

请帮我爱他 2024-11-17 07:12:36

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

事实证明,这里的问题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,其中 InputOutput 实际上是同一类型)它只会绑定到 conforms:<:<[Input,Output]:在这种情况下又充当身份的函数。

(有关#4案例的更多信息,请参阅这个明显相关的问题:问题与我的方法和符合之间隐含的歧义预定义。)

(Note: this is a summary of the discussion that took place in possibly more detail in the comments section of another answer on this question.)

It turns out that the problem here is that the implicit parameter is not first in runAny, but not because the implicit binding mechanism is ignoring it: instead, the issue is that the type parameter Output is not bound to anything, and needs to be indirectly inferred from the type of the run implicit parameter, which is happening "too late".

In essence, the code for "undetermined type parameters" (which is what Output is in this circumstance) only gets used in situations where the method in question is considered to be "implicit", which is determined by its direct parameter list: in this case, runAny's parameter list is actually just (input :Input), and isn't "implicit".

So, the type parameter for Input manages to work (getting set to Int::HNil), but Output is simply set to Nothing, which "sticks" and causes the type of the run argument to be Int::HNil=>Object=>Nothing, which is not satisfiable by runNil, causing runAny's type inferencing to fail, disqualifying it for usage as an implicit argument to runAll.

By reorganizing the parameters as done in my modified code sample #2, we make runAny itself be "implicit", allowing it to first get its type parameters fully determined before applying its remaining arguments: this happens because its implicit argument will first get bound to runNil (or runAny again for more than two levels), whose return type will get taken/bound.

To tie up the loose ends: the reason that the code sample #3 worked in this situation is that the Output parameter wasn't even required: the fact that it was bound to Nothing didn't affect any subsequent attempts to bind it to anything or use it for anything, and runNil was easily chosen to bind to its version of the run implicit parameter.

Finally, code sample #4 was actually degenerate, and shouldn't even have been considered to "work" (I had only verified that it compiled, not that it generated the appropriate output): the data types of its implicit parameters were so simplistic (Input=>Output, where Input and Output were actually intended to be the same type) that it would simply get bound to conforms:<:<[Input,Output]: a function that in turn acted as the identity in this circumstance.

(For more information on the #4 case, see this apparently dead-on related question: problem with implicit ambiguity between my method and conforms in Predef.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文