Непоследовательное поведение между (+) и (-) при использовании «встроенного» и оценки предложения - PullRequest
13 голосов
/ 19 сентября 2011

Кто-нибудь знает, почему 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

Обратите внимание, что без встроенного ключевого слова исключение не генерируется (но код не является общим) Кроме того, исключение выдается только при использовании цитат. Нормальная оценка работает отлично.

Спасибо

Редактировать: пример упрощенного кода

1 Ответ

13 голосов
/ 19 сентября 2011

Спасибо за этот вопрос - это действительно хороший отчет об ошибке с простым воспроизведением, и я не мог в это поверить, но вы совершенно правы. Плюс работает, а минус - нет.

Проблема в том, что 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 (см. почта ).

...