C# と F# では、既定 (またはオプション) パラメーターの実装が異なります。
C# 言語では、デフォルト値を引数に追加しても、基になる型 (つまり、パラメーターの型) は変更されません。実際、C# のオプションの引数は軽量な構文糖衣です。
class CSharpOptionalArgs
{
public static void Foo(int n = 0) {}
}
// Somewhere in the call site
CSharpOptionalArgs.Foo();
// Call to Foo() will be transformed by C# compiler
// *at compile time* to something like:
const int nArg = GetFoosDefaultArgFromTheMetadata();
CSharpOptionalArgs.Foo(nArg);
しかし、F# はこの機能を別の方法で実装します。C# とは異なり、F# のオプションの引数は呼び出し先サイトでは解決されますが、呼び出し元サイトでは解決されません。
type FSharpOptionalArgs() =
static let defaultValue() = 42
static member public Foo(?xArg) =
// Callee site decides what value to use if caller does not specifies it
let x = defaultArg xArg (defaultValue())
printfn "x is %d" x
この実装は絶対に合理的で、はるかに強力です。C# のオプションの引数は、コンパイル時の定数のみに制限されています (オプションの引数はアセンブリ メタデータに格納されているため)。F# では、既定値はそれほど明白ではありませんが、任意の式を既定値として使用できます。ここまでは順調ですね。
F# コンパイラによって変換された F# のオプションの引数Microsoft.FSharp.Core.FSharpOption<'a>
は、参照型です。これは、F# でオプションの引数を使用してメソッドを呼び出すたびに、マネージ ヘッドで追加の割り当てが発生し、ガベージ コレクションへのプレッシャーが発生することを意味します。
**EDITED**
// This call will NOT lead to additional heap allocation!
FSharpOptionalArgs.Foo()
// But this one will do!
FSharpOptionalArgs.Foo(12)
アプリケーション コードについては心配していませんが、この動作によってライブラリのパフォーマンスが大幅に低下する可能性があります。オプションの引数を持つライブラリ メソッドが 1 秒間に何千回も呼び出される場合はどうなるでしょうか。
この実装は私には本当に奇妙に思えます。しかし、ライブラリ開発者がこの機能の使用を避けるべきいくつかのルールがあるのでしょうか、それとも F# チームが F# の将来のバージョンでこの動作を変更する予定なのでしょうか?
オプションの引数が参照型であるという単体テストの教授に続きます。
[<TestFixture>]
type FSharpOptionalArgumentTests() =
static member public Foo(?xArg) =
// Callee site decides what value to use if caller does not specifies it
let x = defaultArg xArg 42
()
[<Test>]
member public this.``Optional argument is a reference type``() =
let pi = this.GetType().GetMethod("Foo").GetParameters() |> Seq.last
// Actually every optional parameter in F# is a reference type!!
pi.ParameterType |> should not' (equal typeof<int>)
pi.ParameterType.IsValueType |> should be False
()