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


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

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

4 59

4 ответа:

The 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)

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

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

когда я должен использовать 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) случае, функция является статически (и часто неявно) типизированной.

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

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.

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

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

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

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