13

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

TimC
  • 412
  • 2
  • 8
  • Try not to use the PowerPack's eval methods. If you really-really have to, there are other ways to eval quotations. For example, Stephen Swensen's Unquote (http://code.google.com/p/unquote/). – Ramon Snir Sep 19 '11 at 13:54
  • @Ramon - while I appreciate the endorsement, and I do agree that Unquote's evaluator can be a better choice in a lot of scenarios (faster non-compiled evaluation, supports more quotation patterns, supports Silverlight 4, probably less buggy by virtue of being simpler), I do need to point out that this issue is deeper than any evaluation engine can handle and so can be seen in Unquote's evaluator too: the troublesome call to the `NoDynamicInvocation` operator `-` is buried away at compile time and can't be worked around. – Stephen Swensen Sep 19 '11 at 15:45
  • 1
    I filed a bug with the PowerPack project for basically the same issue a while back (though I didn't understand that `-` was the issue at the time): http://fsharppowerpack.codeplex.com/workitem/5882, but now I believe this is something that needs to be reported directly to the compiler team. – Stephen Swensen Sep 19 '11 at 16:22

1 Answers1

13

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

Community
  • 1
  • 1
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Why doesn't `let inline sub x = x + (-x)` work either? Same reason, no dynamic impl for unary negation? – Daniel Sep 19 '11 at 14:17
  • @Daniel - yep. After looking around (search for `DynamicImplTable` to get a list of all operations that support dynamic implementation), I don't think there is a way to do that. You can use `+`, `*`, generic one, generic zero, `sign` and a few others. I don't see how to build subtraction using this :-) – Tomas Petricek Sep 19 '11 at 14:30
  • 2
    I'm actually painfully aware of this issue having implemented a quotation evaluation engine for Unquote. There are *lots* of core operators lacking dynamic invocation support and there doesn't seem to be any consistency in choices for those which have them and those which don't. That means any quotation evaluation engine must implement custom dynamic resolution for those lacking (performance is another compelling reason to do it anyways though, especially for numeric operators): http://code.google.com/p/unquote/source/browse/tags/2.1.0/Unquote/DynamicOperators.fs – Stephen Swensen Sep 19 '11 at 16:03
  • @Tomas - thank you very much for such a clear and thorough explanation! – TimC Sep 19 '11 at 16:36