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

Tim*_*imC 13 f# inline quotations

有没有人知道为什么不sub抛出异常add?这是一个错误吗?

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

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

谢谢

编辑:简化代码示例

Tom*_*cek 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
Run Code Online (Sandbox Code Playgroud)

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

我不知道为什么会这样 - 我认为没有任何技术原因,但它解释了为什么你得到你报告的行为.如果您使用ILSpy查看已编译的代码,您将看到该add方法执行某些操作并且该sub方法只是抛出(因此这是异常的来源).

至于解决方法,您需要以不使用泛型减运算符的方式编写代码.可能最好的选择是避免inline函数(通过使用sub_intsub_float)或编写自己的动态实现sub(可以使用DLR非常有效地完成(参见本文).

  • 我实际上已经痛苦地意识到这个问题已经实现了Unquote的报价评估引擎.有许多核心运营商缺乏动态调用支持,并且对于拥有它们的那些和没有它们的那些选择似乎没有任何一致性.这意味着任何报价评估引擎都必须为那些缺乏的人提供自定义动态解析(性能是另一个令人信服的理由,不管怎样,特别是对于数字运算符):http://code.google.com/p/unquote/source/browse/标签/ 2.1.0 /引文结束/ DynamicOperators.fs (2认同)