Подтвердить что ты не робот

Сравнивать работу по-разному с дженериками

Я наткнулся на какое-то "странное поведение". Я использовал интерактивный F # для проверки кода и написал

Seq.zip "ACT" "GGA" |> Seq.map ((<||) compare)
// val it : seq<int> = seq [-1; -1; 1]

Затем я хотел сделать из него функцию и написал

let compute xs ys = Seq.zip xs ys |> Seq.map ((<||) compare)
// val compute : xs:seq<'a> -> xs:seq<'a> -> seq<int> when 'a : comparison

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

compute "ACT" "GGA"
// val it : seq<int> = seq [-6; -4; 19]

Итак, как-то compare действует по-разному для "той же вещи", когда есть другая "точка зрения" (явный тип vs generics)

Я знаю, как его решить: либо явным явлением типа

let compute (xs: #seq<char>) // ... or char seq or string

Или сохранение типа generic и составление с помощью функции sign

let compute (* ... *) ((<||) compare >> sign)

tl; dr вопрос заключается в том, где разница в поведении происходит точно?

4b9b3361

Ответ 1

Это сложное взаимодействие между оптимизацией компилятора F # и оптимизацией стандартной библиотеки .NET.

Во-первых, F # пытается оптимизировать вашу программу. Когда типы известны во время компиляции, а типы примитивны и сопоставимы, вызов compare компилируется только для сравнения. Поэтому сравнение символов в вашем примере будет выглядеть как if 'A' < 'G' then -1 elif 'A' > 'G' then 1 else 0.

Но когда вы переносите вещь в общий метод, вы удаляете информацию о типе. Теперь типы являются обобщенными, компилятор не знает, что они char. Поэтому компилятор вынужден вернуться к вызову HashCompare.GenericComparisonIntrinsic, который в свою очередь вызывает IComparable.CompareTo аргументы.

А теперь угадайте, как IComparable реализован в типе char? Он просто вычитает значения и возвращает результат. Серьезно, попробуйте это на С#:

Console.WriteLine( 'A'.CompareTo('G') ); // prints -6

Обратите внимание, что такая реализация IComparable технически не является ошибкой. Согласно документации, он не должен возвращать только [-1,0,+1], он может возвращать любое значение, пока его знак правильный. Мое лучшее предположение заключается в том, что это также делается для оптимизации.

F # документация для compare не указывает это вообще. Он просто говорит "результат сравнения" - переходите к тому, что должно быть: -)


Если вы хотите, чтобы ваша функция compute возвращала только [-1,0,+1], это можно легко достичь, выполнив функцию inline:

let inline compute xs ys = Seq.zip xs ys |> Seq.map ((<||) compare)

Теперь он будет расширяться на сайте вызова, где известны типы, и может быть вставлен оптимизированный код. Имейте в виду, что, поскольку в документах поведение [-1,0,+1] не гарантируется, оно может исчезнуть в будущем. Поэтому я бы предпочел не полагаться на это.