使用“内联”和引号评估时,(+)和(-)之间的行为不一致

13

有人知道为什么sub会抛出异常而add不会吗?这是个bug吗?

open Microsoft.FSharp.Linq.QuotationEvaluation

let inline add x = x + x
let inline sub x = x - x

let answer  = <@ add 1 @>.Eval() // 2, as expected
let answer2 = <@ sub 1 @>.Eval() // NotSupportedException

注意,如果没有使用inline关键字,则不会抛出异常(但代码不是通用的)。 此外,只有在使用引号时才会抛出异常。正常评估工作正常。

谢谢

编辑:简化的代码示例


尽量不要使用PowerPack的eval方法。如果你真的非常需要,还有其他评估引用的方法。例如,Stephen Swensen的Unquote(http://code.google.com/p/unquote/)。 - Ramon Snir
@Ramon - 虽然我很感激你的认可,我也同意在许多情况下Unquote的评估器可能是更好的选择(更快的非编译评估,支持更多引用模式,支持Silverlight 4,可能由于更简单而不那么有bug),但我需要指出这个问题比任何评估引擎都要深入,因此在Unquote的评估器中也可以看到:对NoDynamicInvocation操作符“-”的麻烦调用被隐藏在编译时,并且无法解决。 - Stephen Swensen
1
我之前曾向PowerPack项目报告过一个基本上相同的问题(尽管当时我不知道“-”是问题所在):http://fsharppowerpack.codeplex.com/workitem/5882,但现在我认为这是需要直接向编译器团队报告的问题。 - Stephen Swensen
1个回答

13

感谢您提出这个问题——这是一个非常好的错误报告,具有简单的重现方式,我不敢相信,但您完全正确。加法可以,但减法不行。

问题在于subadd被编译为通用方法,而LINQ版本调用这些通用方法。内联是在引号存储之后执行的,因此引号代码包含对sub方法的调用。在正常的F#代码中,这不是问题,因为函数被内联,运算符被解析为某些数值类型上的+或-。

然而,通用版本使用动态查找。如果您查看prim-types.fs:3530,您会看到:

let inline (+) (x: ^T) (y: ^U) : ^V = 
  AdditionDynamic<(^T),(^U),(^V)>  x y 
  when ^T : int32       and ^U : int32      = (# "add" x y : int32 #)
  when ^T : float       and ^U : float      = (# "add" x y : float #)
  // ... lots of other cases

AdditionDynamic是从通用方法中调用的函数,它执行动态查找,虽然较慢,但能正常工作。有趣的是,在减法运算符的情况下,F#库不包含动态实现:

[<NoDynamicInvocation>]
let inline (-) (x: ^T) (y: ^U) : ^V = 
  ((^T or ^U): (static member (-) : ^T * ^U -> ^V) (x,y))
  when ^T : int32      and ^U : int32      = (# "sub" x y : int32 #)
  when ^T : float      and ^U : float      = (# "sub" x y : float #)
  // ... lots of other cases

我不知道为什么会出现这种情况——我认为没有任何技术原因,但这解释了你报告的行为。如果你使用ILSpy查看已编译的代码,你会发现add方法有一些动作,而sub方法只会抛出异常(这就是异常的来源)。
至于解决方法,你需要以一种不使用泛型减号操作符的方式编写代码。可能最好的选择是避免使用内联函数(可以通过使用sub_intsub_float),或者编写自己的sub动态实现(这可以通过使用DLR来实现,具体请参见此帖子)。

为什么 let inline sub x = x + (-x) 也不起作用呢?同样的原因,没有一元取反的动态实现吗? - Daniel
@Daniel - 是的。在查找了一番后(搜索“DynamicImplTable”以获取支持动态实现的所有操作列表),我认为没有办法做到这一点。您可以使用+*,通用的1,通用的0,sign和其他一些方法。我不知道如何使用这些方法构建减法 :-) - Tomas Petricek
2
我非常清楚这个问题,因为我为Unquote实现了一个引号评估引擎。有很多核心运算符缺乏动态调用支持,并且在那些具有和不具有动态调用支持的运算符中似乎没有任何一致性选择。这意味着任何引号评估引擎都必须为那些缺少自定义动态解析(性能是另一个强制执行它的令人信服的原因,特别是对于数字运算符):http://code.google.com/p/unquote/source/browse/tags/2.1.0/Unquote/DynamicOperators.fs - Stephen Swensen
@Tomas - 非常感谢您提供如此清晰和详尽的解释! - TimC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接