Непоследовательное поведение между (+) и (-) при использовании «встроенной» оценки и оценки цитаты

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

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

Спасибо

Изменить: упрощенный пример кода


person TimC    schedule 19.09.2011    source источник
comment
Старайтесь не использовать методы оценки PowerPack. Если вам действительно нужно, есть и другие способы оценки котировок. Например, Unquote Стивена Свенсена (code.google.com/p/unquote).   -  person Ramon Snir    schedule 19.09.2011
comment
@Ramon - хотя я ценю одобрение и согласен с тем, что оценщик Unquote может быть лучшим выбором во многих сценариях (более быстрая некомпилируемая оценка, поддерживает больше шаблонов кавычек, поддерживает Silverlight 4, вероятно, меньше ошибок в силу того, что он проще ), я должен указать, что эта проблема глубже, чем может обработать любой механизм оценки, и поэтому ее можно увидеть и в оценщике Unquote: неприятный вызов оператора NoDynamicInvocation - скрыт во время компиляции, и его нельзя обойти. .   -  person Stephen Swensen    schedule 19.09.2011
comment
Некоторое время назад я отправил сообщение об ошибке в проекте PowerPack по той же самой проблеме (хотя в то время я не понимал, что проблема была в -): fsharppowerpack.codeplex.com/workitem/5882, но теперь я считаю, что об этом нужно сообщить непосредственно команде компилятора.   -  person Stephen Swensen    schedule 19.09.2011


Ответы (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 (см. person Tomas Petricek    schedule 19.09.2011

comment
Почему let inline sub x = x + (-x) тоже не работает? По той же причине, нет динамического внедрения для унарного отрицания? - person Daniel; 19.09.2011
comment
@ Даниэль - да. Оглядевшись (ищите DynamicImplTable, чтобы получить список всех операций, поддерживающих динамическую реализацию), я не думаю, что есть способ сделать это. Вы можете использовать +, *, общий номер один, общий ноль, sign и некоторые другие. Я не понимаю, как построить вычитание, используя это :-) - person Tomas Petricek; 19.09.2011
comment
@Tomas - большое спасибо за такое ясное и подробное объяснение! - person TimC; 19.09.2011