为什么F#(FSharpOption <T>)中的默认参数是引用类型?

Ser*_*kov 10 f# optional-arguments

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

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

这种实现绝对合理且功能更强大.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)
Run Code Online (Sandbox Code Playgroud)

我不担心应用程序代码,但这种行为可能会大大降低库的性能.如果某个带有可选参数的库方法每秒会被调用数千次呢?

这个实现对我来说似乎很奇怪.但也许有一些规则,图书馆开发人员应避免使用此功能,否则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
        ()
Run Code Online (Sandbox Code Playgroud)

con*_*low 5

因为F#团队中的任何人都没有兴趣将"简单",Option类似的歧视联盟编译为值类型,支持这些联合的模式匹配等等:)

请记住,元组类型在F#等函数语言中被大量使用(远远超过默认参数),但仍然在CLR中作为引用类型实现 - 没有人关心内存分配和特定于功能语言的GC调整.

  • 很明显,在不久的将来,没有人会将CLR仅调整为一种特定的语言,特别是对于F#.但是在这种情况下可以相对容易地"调整"语言.例如,Nemerle对元组使用简单优化,并将"小"元组(我认为少于4个args)作为结构和"大"元组作为类进行编译.似乎可以对元组和可选参数使用类似的优化. (2认同)