Почему аргументы по умолчанию в 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