Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 657
TimC Avatar asked Sep 19 '11 13:09

TimC


1 Answers

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

like image 174
Tomas Petricek Avatar answered Nov 16 '22 03:11

Tomas Petricek