在F#中使用`inline`

F#中的inline关键字在我看来似乎与我在C中使用的有所不同。例如,它似乎影响函数的types(什么是“静态parsing的types参数”?不是所有的F#types静态parsing?)

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

inline关键字指示应将函数定义内联插入到使用它的任何代码中。 大多数情况下,这对函数的types没有任何影响。 然而,在极less数情况下,它可能会导致一个更通用的函数,因为.NET中的代码的编译forms存在一些限制,而这些限制在函数内联时可以强制执行。

这适用的主要情况是使用操作员。

 let add ab = a + b 

将有一个单形推断的types(可能是int -> int -> int ,但它可以是像float -> float -> float如果您有代码使用此types的函数)。 但是,通过内联标记此函数,F#编译器将推断出多态types:

 let inline add ab = a + b // add has type ^a -> ^b -> ^c when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c) 

在.NET的编译代码中,没有办法以一stream的方式编码这种types的约束。 但是,F#编译器可以在内联函数所在的位置执行此约束,以便在编译时parsing所有操作符用法。

types参数^a^b^c是“静态parsingtypes参数”,这意味着参数types必须在正在使用这些参数的站点上静态地知道。 这与正常的types参数(例如'a'b ”等)形成对比,其中参数表示类似于“稍后将提供的一些types,但可以是任何types”的东西。

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

inline关键字在实践中最有价值的应用是将高阶函数内联到函数参数也被内联的调用位置,以便产生一个单独完全优化的代码片段。

例如,下面的fold函数中的inline使得它快5倍:

  let inline fold fa (xs: _ []) = let mutable a = a for i=0 to xs.Length-1 do a <- fa xs.[i] a 

请注意,这与大多数其他语言的inlinefunction几乎没有什么相似之处。 您可以使用C ++中的模板元编程实现类似的效果,但F#也可以在编译的程序集之间内联,因为inline是通过.NET元数据传送的。

当你需要定义一个函数时,你应该使用内联,而函数必须在每次使用的地方进行types(重新)评估,而不是正常的函数,它的types只能在第一次使用的地方进行评估(推断) ,然后被认为是在其他地方的其他地方静态地input了第一个推断的types签名。

在内联的情况下,函数定义是有效的generics/多态,而在正常(非内联)的情况下,函数是静态的(通常是隐含的)types。

所以,如果你使用内联,下面的代码:

 let inline add ab = 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 

将build立,并编译产生以下输出:

 3 dogcat 

换句话说,同样的add函数定义已经被用来产生一个添加到整数的函数,还有一个连接两个string的函数(实际上底层操作符在+上的重载也是通过内联实现的)。

鉴于这个代码,除了add函数不再被声明为内联以外,完全相同:

 let add ab = 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 

使用内联的一个很好的例子是,当你想定义一个generics函数来颠倒2个参数的函数参数的应用顺序时,例如

 let inline flip fxy = fyx 

就像在@pad对这个问题的答案中所做的那样。 获取Array,List或Seq的第N个元素的不同的参数顺序 。

F#组件devise指南仅提及一点。 我的build议(与那里所说的一致)是:

  • 不要使用inline
    • 例外情况:当编写math库被其他F#代码使用时,您可能会考虑使用inline ,并且您希望编写通用于不同数字数据types的函数。

对于“鸭子打字”types的工作有点像C ++模板,内联和静态成员约束有很多其他“有趣的”用法。 我的build议是避免像瘟疫一样。

@ kvb的答案更深入地讨论了什么是“静态types约束”。