在 F # 中使用‘ inline’

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

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

10336 次浏览

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等)形成了对比,在这些参数中,参数的意思类似于“稍后将提供的某种类型,但可以是任何类型”。

F # 组件设计指南只提到了一点,我的建议(与上面所说的一致)是:

  • 不要用 inline
    • 异常: 在编写数学库供其他 F # 代码使用时,您可能会考虑使用 inline,并且希望编写基于不同数值数据类型的通用函数。

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

@kvb's answer goes into more depth about what 'static type constraints' are.

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

inline关键字在实践中最有价值的应用是将高阶函数内联到调用站点,在那里它们的函数参数也内联,以生成一段完全优化的代码。

例如,在下面的 fold函数中的 inline使它5 & # 215; 更快:

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

Note that this bears little resemblance to what inline does in most other languages. You can achieve a similar effect using template metaprogramming in C++ but F# can also inline between compiled assemblies because inline is conveyed via .NET metadata.

当你需要定义一个函数时,你应该使用内联,这个函数的类型必须在每次使用的位置进行(重新)计算,而不是一个普通的函数,这个函数只在第一次使用的位置进行类型计算(推断) ,然后在其他任何地方被认为是静态类型的第一个推断类型签名。

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

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

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,即一个将(其他)函数作为参数的函数) ,例如一个通用函数来颠倒一个有两个参数的函数的参数的应用顺序,例如。

let inline flip f x y = f y x

正如从@pad 到这个问题 Different argument order for getting N-th element of Array, List or Seq的答案中所做的那样。