10

C# and F# has different implementation of the default (or optional) parameters.

In C# language when you add default value to the argument you'll not change its underlying type (I mean type of the parameter). Actually optional arguments in C# is a lightweight syntactic sugar:

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

But F# implements this feature in a different way. Unlike C#, F# optional arguments resolves at callee site but not at caller site:

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

This implementation is absolutely reasonable and much more powerful. C# optional arguments restricted only to compile-time constants (because optional arguments stored in assemblies metadata). In F# default value could be less obvious but we can use arbitrary expression as a default value. So far so good.

F# optional arguments transformed by F# compiler to Microsoft.FSharp.Core.FSharpOption<'a> which is a reference type. This means that every call to the method with optional argument in F# will lead to additional allocation at the managed head and will lead to pressure to garbage collection.

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

I don't worry about application code, but this behavior could dramatically degrade performance for libraries. What if some library method with an optional argument will be called thousands times per second?

This implementation seems really odd to me. But maybe there is some rules that library developer should avoid using of this feature or F# team is going to change this behavior in the future version of F#?

Following unit test profs that optional arguments are reference type:

[<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
        ()
Sergey Teplyakov
  • 11,477
  • 34
  • 49
  • 3
    For the sake of completeness: Option is annotated with http://msdn.microsoft.com/en-us/library/ee370528.aspx, so if argument is omitted - None=null will be passed instead. – desco Apr 13 '13 at 16:43
  • So this means that for "default" values we'll not allocate any objects in managed heap, but for "non-default" values we'll do? – Sergey Teplyakov Apr 13 '13 at 19:18

2 Answers2

5

Because nobody in F# team is not interesting yet in compiling "simple", Option-like discriminated unions as value types, supporting pattern-matching over such unions and so on :)

Remember, tuples types are heavly used in functional languages like F# (much more than default arguments), but still implemented in CLR as refererence types - nobody cares about memory allocation and functional_languages-specific GC tweaks.

controlflow
  • 6,667
  • 1
  • 31
  • 55
  • I should clarify that F# is able expand typle types into multiple parameters where possible, but return values of tuple types and tuple usage in high-order functions (like `Seq.map (fun (x, y) -> x + y)`) always produces heap allocations and GC pressure. – controlflow Apr 13 '13 at 19:52
  • 2
    Its clear that no one in the near future will tune CLR only to one particular language, especially for F#. But its possible to "tune" language in this case relatively easyly. For example, Nemerle uses simple optimization for tuples and complile "small" tuples (with less than 4 args, I think) as structs and "large" tuples as classes. It seems possible to use similar optimization for tuples and optional arguments as well. – Sergey Teplyakov Apr 15 '13 at 09:51
  • Don't forget about struct tuples! – sdgfsdh Aug 13 '22 at 21:51
1

F# 4.1 has C# optional attribute compatibility. However if you are developing function that will be consumed also by F# then you should use regular syntax:

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

F# 4.1 optional arguments documentation: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/members/methods