【发布时间】:2010-09-20 19:35:58
【问题描述】:
在我看来,F# 中的 inline 关键字的用途与我习惯的用途有所不同,例如C. 比如,它似乎会影响一个函数的类型(什么是“静态解析的类型参数”?不是所有的 F# 类型都是静态解析的吗?)
我应该什么时候使用inline 函数?
【问题讨论】:
在我看来,F# 中的 inline 关键字的用途与我习惯的用途有所不同,例如C. 比如,它似乎会影响一个函数的类型(什么是“静态解析的类型参数”?不是所有的 F# 类型都是静态解析的吗?)
我应该什么时候使用inline 函数?
【问题讨论】:
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 等)形成对比,其中参数的含义类似于“稍后将提供的某种类型,但可以是任何类型”。
【讨论】:
p:'a - 要求 p 是 'a 类型的后代,例如 int、obj、'a when 'a :> SomeBaseType 等。另一方面,p:^a -要求 p 是在代码中的调用点支持某些特性或子类型的类型,方法是直接这样做或打开支持模块或访问扩展方法类等。
当您需要定义必须在每次使用的站点对其类型进行(重新)评估的函数时,您应该使用内联,而不是仅在站点评估(推断)其类型的普通函数首次使用,然后在其后的其他任何地方都被视为使用第一个推断的类型签名进行静态类型化。
在内联情况下,函数定义实际上是泛型/多态的,而在正常(非内联)情况下,函数是静态(通常是隐式)类型的。
所以,如果你使用内联,下面的代码:
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
适合使用 inline 的一个很好的例子是,当您想要定义一个高阶函数(HOF,即一个将(其他)函数作为参数的函数)时,例如一个通用函数,用于反转具有 2 个参数的函数的参数应用顺序,例如
let inline flip f x y = f y x
正如@pad对此问题Different argument order for getting N-th element of Array, List or Seq的回答所做的那样。
【讨论】:
我应该什么时候使用
inline函数?
inline 关键字在实践中最有价值的应用是将高阶函数内联到调用站点,其中它们的函数参数也被内联,以生成单独完全优化的代码。
例如,以下 fold 函数中的 inline 使其速度提高了 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 元数据传达的。
【讨论】:
fold 比内置的 Array.fold 快约 3 倍。
inline 的内置会有缺点吗?
inline 并且(违反直觉)不是因为它执行的内联!
F# component design guidelines 只提到了一点。我的建议(与那里所说的完全一致)是:
inline
inline。对于类似于 C++ 模板的“鸭式”类型的场景,还有许多其他“有趣”的内联和静态成员约束使用。我的建议是像瘟疫一样避免所有这些。
@kvb 的回答更深入地介绍了“静态类型约束”是什么。
【讨论】:
inline 关键字在数学库之外也非常有用。例如,在数据结构的上下文中,它可用于从抽象数据结构组合具体数据结构,而不会导致任何运行时性能损失。