web-dev-qa-db-ja.com

F#での「インライン」の使用

F#のinlineキーワードは、たとえば、私が慣れているものとは多少異なる目的を持っているように見えます。 C.たとえば、関数の型に影響を与えるようです(「静的に解決される型パラメーター」とは何ですか?すべてのF#型が静的に解決されるわけではありませんか?)

いつinline関数を使用する必要がありますか?

66
J Cooper

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など)とは対照的です。パラメータは、「後で提供されるが、何でもかまいません」のようなものを意味します。

77
kvb

いつ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++のテンプレートメタプログラミングを使用して同様の効果を実現できますが、inlineは.NETメタデータを介して伝達されるため、F#はコンパイルされたアセンブリ間でインライン化することもできます。

30
Jon Harrop

最初の使用場所でのみ型が評価(推測)される通常の関数とは対照的に、各使用場所で型を(再)評価する必要がある関数を定義する必要がある場合は、インラインを使用する必要があります、その後、最初に推測された型シグネチャを使用して静的に型付けされたと見なされます。

インラインの場合、関数定義は事実上ジェネリック/ポリモーフィックですが、通常の(インラインなし)場合、関数は静的に(そして多くの場合暗黙的に)型付けされます。

したがって、インラインを使用する場合は、次のコードを使用します。

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関数定義を使用して、整数に加算する関数と2つの文字列を連結する関数の両方を生成しました(実際、+での基になる演算子のオーバーロードは、インラインを使用して内部でも実行されます)。

このコードは、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

インラインを使用することが適切な場合の良い例は、2つの引数を持つ関数の引数の適用順序を逆にするジェネリック関数を定義する場合です。

let inline flip f x y = f y x

@padからこの質問への回答で行われているように 配列、リスト、またはシーケンスのN番目の要素を取得するための異なる引数の順序

27

F#コンポーネントの設計ガイドライン これについては少しだけ言及します。私の推奨事項(そこで言われていることとよく一致します)は次のとおりです。

  • inline [.____を使用しないでください。]
    • 例外:他のF#コードで使用される数学ライブラリを作成するときにinlineの使用を検討し、さまざまな数値データ型でジェネリックな関数を作成する場合があります。

C++テンプレートのように機能する「ダックタイピング」の種類のシナリオでは、インラインおよび静的メンバー制約の「興味深い」使用法が他にもたくさんあります。私のアドバイスは、疫病のようなものすべてを避けることです。

@kvbの答えは、「静的型制約」とは何かについてさらに深く掘り下げています。

7
Brian