Почему аргументы по умолчанию в F# (FSharpOption<T>) являются ссылочными типами?

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)

Я не беспокоюсь о коде приложения, но такое поведение может значительно снизить производительность для библиотек. Что если какой-нибудь библиотечный метод с необязательным аргументом будет вызываться тысячи раз в секунду?

Эта реализация кажется мне действительно странной. Но, может быть, есть некоторые правила, по которым разработчик библиотеки должен избегать использования этой функции, или команда 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
        ()

2 ответа

Потому что никто из команды F# пока не заинтересован в составлении "простого", Option-подобные дискриминационные союзы как типы значений, поддержка сопоставления с образцом для таких союзов и т. д.:)

Помните, что типы кортежей интенсивно используются в функциональных языках, таких как F# (намного больше, чем аргументы по умолчанию), но все еще реализуются в CLR как ссылочные типы - никто не заботится о распределении памяти и настройках GC, зависящих от функциональных_языков.

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

Другие вопросы по тегам