views:

395

answers:

2

Let's look at the code, generated by F# for simple function:

let map_add valueToAdd xs =
    xs |> Seq.map (fun x -> x + valueToAdd)

The generated code for lambda expression (instance of F# functional value) will looks like this:

[Serializable]
internal class map_add@3 : FSharpFunc<int, int> {
    public int valueToAdd;
    internal map_add@3(int valueToAdd) { this.valueToAdd = valueToAdd; }
    public override int Invoke(int x)  { return (x + this.valueToAdd); }
}

And look at nearly the same C# code:

using System.Collections.Generic;
using System.Linq;

static class Program {
    static IEnumerable<int> SelectAdd(IEnumerable<int> source, int valueToAdd) {
        return source.Select(x => x + valueToAdd);
    }
}

And the generated code for the C# lambda expression:

[CompilerGenerated]
private sealed class <>c__DisplayClass1 {
    public int valueToAdd;
    public int <SelectAdd>b__0(int x) { return (x + this.valueToAdd); }
}

So I have some questions:

  • Why is F#-generated class not marked as sealed?
  • Why does F#-generated class contain public fields since F# doesn't allow mutable closures?
  • Why does F# generated class have a constructor? It may be perfectly initialized with the public fields...
  • Why is C#-generated class not marked as [Serializable]? Also classes generated for F# sequence expressions also became [Serializable] and classes for C# iterators do not.
+4  A: 

Since they are compiler-generated, the sealed / public field issues are a bit moot - you shouldn't ever see it except via debug tools - how would you be subclassing it or mutating it, except by stepping around the compiler? If you have that level of debug access you can mutate it anyway (via reflection).

For C# it needs top be a field to allow certain ref / out usage, and to allow correct usage with captured mutable structs (yes, evil, we know). I assume F# is similar here (can you mutate a sub-[sub-[sub-]]member of the captured value?). The members could probably be internal, though.

Re [Serialziable]; why would something that underpins a closure be serializable? Delegates make extremely poor serialization candidates. Maybe the nature of F# means that it is better suited to persisting an operation (mid-flow) to disk - but in general, I wouldn't recommend it. I would have no expectation of these objects (iterators and capture-classes) being serialzable.

Marc Gravell
1. `sealed` keyword may allow some optimization by JIT compiler, because it is statically known that every virtual method can't be overriden.2. F# only allow to pass another `byref<ty>` parameter and F# reference cell as `byref<ty>` parameter, so closure fields itself are really immutable in F# (not the contents of this fields) and really may be completely private.3. Serialization will allow some crazy scenarios with continuations, like saving C# iterator state into SQL database and restoring execution state back. It is very nice that F# supports that, but interesting why not C#...
ControlFlow
@ControlFlow - (1) is pretty minor in the grand scheme. I doubt it would make any noticeable difference, but there's also no good reason it *shouldn't* be sealed, so I *kinda* agree. (3) well, coming at that with my serialization head on, I would rather solve that problem in different ways. I simply don't think I'd value that hugely. YMMV of course. But without understanding all the fields involved, it can't guarantee that the entire graph is serializable - *probably* hence why it isn't marked as such.
Marc Gravell
@MarcGravell - thank you for your answer! (3) - more close to real life scenario: C# code `var t = "hello!"; AppDomain.CreateDomain("test").DoCallBack(() => Console.WriteLine(t));` fails at runtime because of closure is not serializable and the F# version will works as expected... nice, isn't it? :)
ControlFlow
@ControlFlow - indeed, remoting between `AppDomains` is perhaps the main *valid* use of `[Serializable]`. I can't say that I'm going to see this as a problem, though.
Marc Gravell
+3  A: 

Why is F#-generated class not marked as sealed?

Because the compiler doesn't (this is in the end a code generation choice.

Why does F#-generated class contain public fields since F# doesn't allow mutable closures?

You would need to be able to get to a reference to an instance to modify it. But you can't, so being modifiable doesn't matter. And likely this avoids needing to special case where a mutable is captured in a closure.

Why does F# generated class have a constructor? It may be perfectly initialized with the public fields...

Again code generation choice.

Why is C#-generated class not marked as [Serializable]? Also classes generated for F# sequence expressions also became [Serializable] and classes for C# iterators do not.

More code generation choices.

None of these choices is developer visible (i.e. make no difference to client code) it really makes no difference.

Richard