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

Posted

技术标签:

【中文标题】为啥 F# (FSharpOption<T>) 中的默认参数是引用类型?【英文标题】:Why default arguments in F# (FSharpOption<T>) are reference types?为什么 F# (FSharpOption<T>) 中的默认参数是引用类型? 【发布时间】:2013-04-06 00:24:24 【问题描述】:

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# 可选参数在 callee 站点解析,但不在 caller 站点解析:

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&lt;'a&gt;,这是一个引用类型。这意味着每次在 F# 中调用带有可选参数的方法都会导致在托管头部进行额外分配,并将导致垃圾回收压力

**EDITED**
// This call will NOT lead to additional heap allocation!
FSharpOptionalArgs.Foo()
// But this one will do!
FSharpOptionalArgs.Foo(12)

我不担心应用程序代码,但这种行为可能会显着降低库的性能。如果每秒调用数千次带有可选参数的库方法怎么办?

这个实现对我来说真的很奇怪。但也许有一些规则库开发人员应避免使用此功能,或者 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
        ()

【问题讨论】:

为了完整起见:选项用msdn.microsoft.com/en-us/library/ee370528.aspx注释,所以如果参数被省略 - None=null 将改为传递。 所以这意味着对于“默认”值我们不会在托管堆中分配任何对象,但对于“非默认”值我们会这样做? 【参考方案1】:

因为 F# 团队中没有人对编译“简单”不感兴趣,Option-like 将联合区分为值类型,支持对此类联合进行模式匹配等等:)

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

【讨论】:

我应该澄清 F# 能够在可能的情况下将类型类型扩展为多个参数,但是元组类型的返回值和高阶函数(如 Seq.map (fun (x, y) -&gt; x + y))中的元组使用总是会产生堆分配和 GC压力。 很明显,在不久的将来,没有人会将 CLR 仅调整为一种特定的语言,尤其是 F#。但是在这种情况下可以相对容易地“调整”语言。例如,Nemerle 对元组使用简单的优化并将“小”元组(我认为少于 4 个参数)编译为结构,将“大”元组编译为类。似乎也可以对元组和可选参数使用类似的优化。【参考方案2】:

F# 4.1 具有 C# 可选属性兼容性。但是,如果您正在开发 F# 也将使用的功能,那么您应该使用常规语法:

member this.M (?i : int) = let iv = defaultArg i 12 iv + 1

F# 4.1 可选参数文档: https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/members/methods

【讨论】:

以上是关于为啥 F# (FSharpOption<T>) 中的默认参数是引用类型?的主要内容,如果未能解决你的问题,请参考以下文章

opencv的代码 Mat T =Mat(X.rows,X.cols,CV_64F); T.at<Vec3b>(0, 0)[0] = 1.0; 为啥这句话报错?

新手学C问题:为啥我的C语言程序老是闪退?

为啥我的模板不接受初始化列表

delphi程序中为啥运行后程序窗口不显示,进程中有

为啥 `IList<T>` 不从 `IReadOnlyList<T>` 继承?

为啥 %A 文本格式不适用于 F# 中的内部模块?