在F#中使用`inline`

J C*_*per 64 f# types inline

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

inline什么时候应该使用功能?

kvb*_*kvb 78

inline关键字表示一个函数定义应该内联插入其使用的任何代码.大多数情况下,这不会对函数的类型产生任何影响.但是,在极少数情况下,它可以导致具有更通用类型的函数,因为存在不能在.NET中的代码的编译形式中表达的约束,但是可以在内联函数时强制执行.

这适用的主要情况是使用运营商.

let add a b = a + b
Run Code Online (Sandbox Code Playgroud)

将有一个单态推断类型(可能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)
Run Code Online (Sandbox Code Playgroud)

在.NET上的编译代码中,无法以第一类方式编码此类型约束.但是,F#编译器可以在其内联函数的站点强制执行此约束,以便在编译时解析所有运算符使用.

类型参数^a,^b^c是"静态解析的类型参数",这意味着必须在使用这些参数的站点静态地知道参数的类型.这与普通类型参数(例如'a,'b等等)形成对比,其中参数意味着类似"稍后将提供但可以是任何东西的某种类型".

  • 抱歉,但是我刚开始学习F#,我根本听不懂您的答案,您能告诉我一些URL来解释您使用的概念吗? (2认同)
  • ``p:'a`` - 要求``p``是类型``'a``的后代,例如``int``,``obj``,``'a when'a:> SomeBaseType``等.另一方面,``p:^ a`` - 要求``p``是一种类型,通过直接或通过拥有代码中的调用点来支持某些特征或子类型打开支持模块或访问扩展方法类等. (2认同)

Jon*_*rop 31

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
Run Code Online (Sandbox Code Playgroud)

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

  • @J Cooper:不,但它确实将其函数参数转换为优化的闭包,这有助于某些类型.例如,当我应用于复数时,我给出的'fold`比内置的'Array.fold`快〜3倍. (5认同)
  • @J Cooper:唐因为过度内联而担心臃肿,所以他把事情考虑在内.但是,我认为这是可以的,因为C#程序员每次都会手工写出显式循环. (4认同)
  • 男人,快3-5倍......这很多!在开发性能关键库时,您是否曾尝试在标准库折叠上使用此内联折叠? (3认同)
  • 标准库是否折叠内联? (2认同)

dav*_*zen 29

当您需要定义一个必须在每次使用的站点上评估其类型(re)的函数时,您应该使用内联,而不是普通函数,该函数将仅在首次使用的站点评估(推断)其类型,然后被视为静态输入其后的其他地方的第一推断类型签名.

在内联案例中,函数定义实际上是通用/多态的,而在正常(非内联)情况下,函数是静态的(通常是隐式)类型.

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

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
Run Code Online (Sandbox Code Playgroud)

将构建并编译以生成以下输出:

3  
dogcat
Run Code Online (Sandbox Code Playgroud)

换句话说,使用相同的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
Run Code Online (Sandbox Code Playgroud)

不会编译,因此投诉失败:

    let dogcat = add dog cat
                     ^^^ - This expression was expected to have type int
                           but instead has type string
Run Code Online (Sandbox Code Playgroud)

使用内联的合适位置的一个很好的例子是,当你想要定义一个泛型函数来反转带有2个参数的函数的参数的应用顺序时,例如

let inline flip f x y = f y x
Run Code Online (Sandbox Code Playgroud)

正如@pad对这个问题的答案所做的那样 获取Array,List或Seq的第N个元素的不同参数顺序.


Bri*_*ian 7

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

  • 不要用 inline
    • 例外:您可能会考虑inline在编写数学库时使用其他F#代码,并且您希望编写通用于不同数字数据类型的函数.

内联和静态成员约束的许多其他"有趣"用法用于"鸭子打字"类型的场景,有点像C++模板.我的建议是避免所有这些像瘟疫一样.

@kvb的答案更深入地探讨了"静态类型约束"是什么.

  • `inline`关键字在数学库之外也非常有用.例如,在数据结构的上下文中,它可以用于从抽象数据结构组成具体数据结构,而不会导致任何运行时性能损失. (7认同)