Использование `inline` в F #

Ключевое слово inline в F # кажется мне несколько иной целью, чем то, к чему я привык, например, C. Например, это влияет на тип функции (что такое "статически разрешенные параметры типа"? Не все ли типы F # статичны?)

Когда я должен использовать функции inline?

Ответ 1

Ключевое слово inline указывает, что определение функции должно быть вставлено встроенным в любой код, который его использует. В большинстве случаев это не повлияет на тип функции. Однако в редких случаях это может привести к функции, которая имеет более общий тип, поскольку существуют ограничения, которые не могут быть выражены в скомпилированной форме кода в .NET, но которые могут быть применены, когда функция встраивается.

Основной случай, когда это применимо, - использование операторов.

let add a b = a + b

будет иметь мономорфный предполагаемый тип (возможно, int -> int -> int, но он может быть чем-то вроде float -> float -> float, если у вас есть код, который использует эту функцию в этом типе). Однако, пометив эту функцию inline, компилятор 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 и т.д.), Где параметры означают нечто вроде "некоторого типа, который будет предоставлен позже, но который может быть любым".

Ответ 2

Когда я должен использовать функции 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 делает на большинстве других языков. Вы можете добиться аналогичного эффекта, используя метапрограммирование шаблонов в С++, но F # также может встроить между скомпилированными сборками, потому что inline передается через метаданные .NET.

Ответ 3

Вы должны использовать inline, когда вам нужно определить функцию, которая должна иметь свой тип (re), оцененный на сайте каждого использования, в отличие от обычной функции, которая будет оценивать свой тип (вывод) только на сайте первого использования, а затем считаться статически типизированным с этой первой предполагаемой подписью типа везде после этого.

В встроенном случае определение функции является фактически общим/полиморфным, тогда как в нормальном (не-встроенном) случае функция статически (и часто неявно) набирается.

Итак, если вы используете встроенный, следующий код:

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

Другими словами, одно и то же определение функции добавления использовалось для создания как функции, добавляющей к целым числам, так и функции, которая объединяет две строки (на самом деле основная перегрузка оператора на + также достигается под капотом с использованием встроенного).

В то время как этот код идентичен, за исключением того, что функция добавления больше не объявлена ​​inline:

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 является подходящим, является то, когда вы хотите определить общую функцию, чтобы отменить порядок применения аргументов функции с двумя аргументами, например

let inline flip f x y = f y x

как это делается в ответе от @pad на этот вопрос Разный порядок аргументов для получения N-го элемента массива, списка или Seq.

Ответ 4

Рекомендации по разработке компонентов F # только немного об этом говорят. Моя рекомендация (которая хорошо согласуется с тем, что там сказано):

  • Не используйте inline
    • Исключение: вы можете использовать inline при написании математических библиотек, которые будут использоваться другим кодом F #, и вы хотите писать функции, которые являются общими для разных типов числовых данных.

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

@kvb отвечает более подробно о том, что такое "ограничения статического типа".