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

10

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# 可选参数在 调用方 而非 被调用方 处解析:

但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)

我不担心应用程序代码,但这种行为可能会严重降低库的性能。如果某个具有可选参数的库方法每秒被调用数千次会怎么样?

我觉得这种实现方式确实很奇怪。但也许有一些规则要求库开发人员避免使用此功能,或者 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
        ()

3
为了完整起见:选项被注释为http://msdn.microsoft.com/en-us/library/ee370528.aspx,因此如果省略参数-将传递None=null。 - desco
这意味着对于“默认”值,我们不会在托管堆中分配任何对象,但对于“非默认”值,我们会这样做? - Sergey Teplyakov
2个回答

5
由于F#团队中还没有人对编译“简单”的Option-like鉴别联合感兴趣,因此需要将其作为值类型支持模式匹配等功能:)
请记住,元组类型在像F#这样的函数语言中被广泛使用(比默认参数更多),但仍然在CLR中实现为引用类型——没有人关心内存分配和特定于函数语言的GC调整。

我应该澄清一下,F# 可以将元组类型扩展为多个参数(在可能的情况下),但返回元组类型的值以及在高阶函数中使用元组(例如 Seq.map (fun (x, y) -> x + y))总是会产生堆分配和垃圾回收压力。 - controlflow
2
很明显,在不久的将来,没有人会只调整CLR以适应某一特定语言,特别是对于F#而言如此。但在这种情况下,“调整”语言相对容易。例如,Nemerle对元组使用简单的优化,并将“小型”元组(少于4个参数,我认为)编译为结构体,而将“大型”元组编译为类。同样可以使用类似的优化来处理元组和可选参数。 - Sergey Teplyakov
不要忘记结构元组! - sdgfsdh

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接