在F#中使用`inline`


71

inline在我看来,F#中的关键字的用途与我在C中所使用的关键字有所不同。例如,它似乎影响一个函数的类型(“静态解析的类型参数是什么”?不是所有F#类型吗?静态解决?)

什么时候应该使用inline函数?

Answers:


81

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等)相反,在常规类型的参数中,这些参数表示“稍后将提供的某种类型,但可以是任何类型”。


3
抱歉,但是我才刚刚开始学习F#,我根本听不懂您的回答,您能告诉我一些URL来解释您使用的概念吗?
knocte 2014年

2
p:'a-需要p是类型的后代'a,例如intobj'a when 'a :> SomeBaseType等。另一方面,p:^a-要求p是支持直接这样做或由一些特征或亚型在代码的调用点具有敞开的支撑模块类型或访问扩展方法类等
乔治

39

当您需要定义必须在每种用法的位置(重新)评估其类型的函数时,应该使用内联,而不是仅在初次使用的位置对其类型进行评估(推断)的普通函数。 ,然后将其视为使用此第一个推断出的类型签名进行静态类型化,此后在其他任何地方进行。

在内联的情况下,函数定义实际上是通用/多态的,而在普通(无内联)情况下,函数是静态(并且通常是隐式)类型的。

因此,如果使用内联,则以下代码:

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函数:

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

当您要定义高阶函数(HOF,即以(其他)函数作为参数的函数)时,可以使用内联的一个很好的例子,例如,一个通用函数可以颠倒a具有2个参数的函数,例如

let inline flip f x y = f y x

就像@pad对此问题的回答一样,获取Array,List或Seq的第N个元素的参数顺序不同


33

什么时候应该使用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元数据传递的。


2
内联标准库折叠吗?
J库珀

5
@J Cooper:否,但是确实将其函数参数转换为对某些类型有帮助的优化闭包。例如foldArray.fold当应用于复数时,我给出的I比内置函数快3倍。
JD,

4
@J库珀:唐担心由于过多的内联而导致膨胀,因此他将事情做好了充分的考虑。但是,我认为这是可以的,因为C#程序员每次都会手动写出显式循环。
JD 2010年

3
伙计,快3-5倍,好多了!在开发性能关键型库时,您是否曾经在标准库折叠中使用此内联折叠?
斯蒂芬·斯文森,2010年

1
@斯蒂芬:是的,一直。我们使用inline很多,并且(违反直觉)不是因为它执行内联!
JD,

9

F#组件的设计准则只提一个关于这个小。我的建议(与那里所说的一致)是:

  • 不要使用 inline
    • 例外:inline编写数学库以供其他F#代码使用时,您可能会考虑使用,而您想编写对不同数字数据类型通用的函数。

内联和静态成员约束还有许多其他“有趣的”用法,用于类似于C ++模板的“鸭式”场景。我的建议是避免所有类似瘟疫的事件。

@kvb的答案更深入地说明了什么是“静态类型约束”。


7
inline关键字也超出数学库是非常有用的。例如,在数据结构的上下文中,可以使用它来由抽象的数据结构构成具体的数据结构,而不会造成任何运行时性能损失。
JD

1
我应该如何在没有内联的情况下实现类型类?
Luiz Felipe
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.