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

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

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

4 ответов


на 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)

нет способа кодировать это ограничение типа способом первого класса в скомпилированном коде .Сеть. Однако компилятор F# может применить это ограничение на сайте где он выравнивает функцию, так что все использования оператора разрешаются во время компиляции.

параметры типа ^a, ^b и ^c являются "статически разрешенными параметрами типа", что означает, что типы аргументов должны быть статически известны на сайте, где эти параметры используются. Это в отличие от нормальных параметров типа (например,'a, 'b, etc.), где параметры означают что - то вроде " некоторый тип, который будет предоставлен позже, но который может будь кем угодно".


когда я должен использовать 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.


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

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

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

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

let inline flip f x y = f y x

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


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

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

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

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