Inconsistent behaviour between (+) and (-) when using 'inline' and quotation evaluation
Does anyone know why 开发者_运维问答sub
throws an exception when add
does not? And is this a 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
Note, without the inline keyword the exception is not thrown (but the code is not generic) Also, the exception is only thrown when using quotations. Normal evaluation works fine.
Thanks
Edit: simplified code example
Thanks for this question - it is a really nice bug report with a simple repro and I couldn't believe this, but you're completely right. Plus works, but minus doesn't.
The problem is that sub
and add
are compiled as generic methods and the LINQ version invokes these generic methods. The inlining is performed after quotations are stored, so the quoted code contains call to the sub
method. This isn't a problem in normal F# code, because the functions are inlined and the operators are resolved to + or - over some numeric types.
However, the generic version uses a dynamic lookup. If you look into prim-types.fs:3530
, you'll see:
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
The AdditionDynamic
is what gets called from the generic method. It does the dynamic lookup, which will be slower, but it will work. Interestingly, for the minus operator, the F# library doesn't include dynamic implementation:
[<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
I have no idea why this is the case - I don't think there is any technical reason, but it explains why you get the behavior you reported. If you look at the compiled code using ILSpy, you'll see that the add
method does something and the sub
method just throws (so this is where the exception comes from).
As for a workaround, you need to write the code in a way in which it doesn't use the generic minus operator. Probably the best option is to avoid inline
functions (either by using sub_int
or sub_float
) or by writing your own dynamic implementation of sub
(which can be done probably quite efficiently using DLR (see this post).
精彩评论