Использование `inline` в F#
inline
мне кажется, что ключевое слово в F# имеет несколько иную цель, чем то, к чему я привык, например, в C. Например, оно влияет на тип функции (что такое "статически разрешаемые параметры типа"? Разве не все типы F# разрешены? статически?)
Когда я должен использовать inline
функции?
4 ответа
inline
Ключевое слово указывает, что определение функции должно быть вставлено встроенным в любой код, который его использует. В большинстве случаев это не повлияет на тип функции. Однако в редких случаях это может привести к функции, которая имеет более общий тип, поскольку существуют ограничения, которые не могут быть выражены в скомпилированном виде кода в.NET, но которые могут быть применены, когда функция встроена.
Основным случаем, когда это применимо, является использование операторов.
let add a b = a + b
будет иметь мономорфный предполагаемый тип (вероятно, int -> int -> int
, но это может быть что-то вроде float -> float -> float
если у вас есть код, который использует эту функцию вместо этого типа). Однако, пометив эту функцию как встроенную, компилятор F# выведет полиморфный тип:
let inline add a b = a + b
// add has type ^a -> ^b -> ^c when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
Невозможно закодировать это ограничение типа первоклассным способом в скомпилированном коде в.NET. Однако компилятор F# может применять это ограничение на сайте, где он указывает функцию, так что все операции оператора разрешаются во время компиляции.
Параметры типа ^a
, ^b
, а также ^c
являются "статически разрешаемыми параметрами типа", что означает, что типы аргументов должны быть статически известны на сайте, где используются эти параметры. Это в отличие от нормальных параметров типа (например, 'a
, 'b
и т. д.), где параметры означают что-то вроде "некоторого типа, который будет предоставлен позже, но который может быть чем угодно".
Вы должны использовать inline, когда вам нужно определить функцию, которая должна иметь (повторно) оцениваемый тип на сайте каждого использования, в отличие от обычной функции, которая будет оценивать свой тип (выводить) только на сайте первого использования. и затем будет рассматриваться как статически типизированный с этой первой предполагаемой сигнатурой типа везде после этого.
В встроенном случае определение функции эффективно является родовым / полиморфным, тогда как в обычном (не встроенном) случае функция статически (и часто неявно) типизирована.
Итак, если вы используете inline, следующий код:
let inline add a b = a + b
[<EntryPoint>]
let main args =
let one = 1
let two = 2
let three = add one two
// here add has been compiled to take 2 ints and return an int
let dog = "dog"
let cat = "cat"
let dogcat = add dog cat
// here add has been compiled to take 2 strings and return a string
printfn "%i" three
printfn "%s" dogcat
0
создаст и скомпилирует для получения следующего вывода:
3
dogcat
Другими словами, одно и то же определение функции добавления использовалось для создания как функции, которая добавляет к целым числам, так и функции, которая объединяет две строки (на самом деле перегрузка базового оператора на + также достигается за счет встроенного расширения).
Принимая во внимание, что этот код идентичен, за исключением того, что функция добавления больше не объявляется встроенной:
let add a b = a + b
[<EntryPoint>]
let main args =
let one = 1
let two = 2
let three = add one two
// here add has been compiled to take 2 ints and return an int
let dog = "dog"
let cat = "cat"
let dogcat = add dog cat
// since add was not declared inline, it cannot be recompiled
// and so we now have a type mismatch here
printfn "%i" three
printfn "%s" dogcat
0
не скомпилируется, если не получится с этой жалобой:
let dogcat = add dog cat
^^^ - This expression was expected to have type int
but instead has type string
Хороший пример того, где использование inline уместно, - это когда вы хотите определить универсальную функцию, чтобы изменить порядок применения аргументов функции с 2 аргументами, например
let inline flip f x y = f y x
как это делается в ответе @pad на этот вопрос. Другой порядок аргументов для получения N-го элемента Array, List или Seq.
Когда я должен использовать
inline
функции?
Наиболее ценное применение inline
Ключевым словом на практике является встраивание функций высшего порядка в сайт вызова, где их аргументы функции также встраиваются, чтобы создать полностью оптимизированный фрагмент кода.
Например, inline
В следующих fold
функция делает это в 5 раз быстрее:
let inline fold f a (xs: _ []) =
let mutable a = a
for i=0 to xs.Length-1 do
a <- f a xs.[i]
a
Обратите внимание, что это мало похоже на то, что inline
делает на большинстве других языков. Вы можете добиться аналогичного эффекта, используя шаблонное метапрограммирование в C++, но F# также может быть встроенным между скомпилированными сборками, потому что inline
передается через метаданные.NET.
В руководстве по проектированию компонентов F# об этом мало говорится. Моя рекомендация (которая хорошо согласуется с тем, что там сказано):
- Не использовать
inline
- Исключение: вы можете рассмотреть возможность использования
inline
при написании математических библиотек, которые будут использоваться другим кодом F#, и вы хотите написать функции, которые являются общими для разных числовых типов данных.
- Исключение: вы можете рассмотреть возможность использования
Существует множество других "интересных" способов использования встроенных и статических ограничений членов для сценариев "типизирования утки", которые работают немного как шаблоны C++. Мой совет - избегать всего этого, как чумы.
Ответ @kvb углубляется в то, что такое "ограничения статического типа".