Ответ 1
Спасибо за этот вопрос - это действительно хороший отчет об ошибке с простым воспроизведением, и я не мог в это поверить, но вы совершенно правы. Плюс работает, но минус нет.
Проблема заключается в том, что sub
и add
скомпилированы как общие методы, а версия 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
просто бросает (так вот откуда исходит исключение).
Что касается обходного пути, вам необходимо написать код таким образом, чтобы он не использовал общий минус-оператор. Вероятно, лучшим вариантом является избежать inline
функций (либо с помощью sub_int
, либо sub_float
), либо путем написания вашей собственной динамической реализации sub
(что может быть сделано, возможно, довольно эффективно с использованием DLR (см. этот пост).