Kurt, have you found out what changed with regard to module initialization? I couldn't find anything related in the release notes, but I'm seeing a constructor of a nested type being accessed and executed before the module containing the type was initialized and I'm pretty sure this is new behaviour...

Stephan

By on 5/23/2009 10:37 AM ()

Sort of.A Module Foo is (since this CTP?) compiled to two static classes: Foo and $Foo. Top level let constants in Foo are compiled to properties on the Foo class, let functions are compiled to static methods returning a FastFunc.Any static initialization (do <whatever>) in the module is not done in the Foo class, and neither are the fields holding the actual object of the let constants. These are in the $Foo class instead. So, something like this:

1
2
3
4
5
6
7
module Foo

let f = printfn "Called f"

let realf() = ignore

do printfn "Foo init"

gets compiled to the following C# equivalent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static class Foo
{
    // Methods
    public static FastFunc<a, Unit> realf<a>()
    {
        return new realf@7<a>();
    }

    // Properties
    [CompilationMapping(SourceConstructFlags.Value)]
    public static Unit f
    {
        get
        {
            return $Foo.f@5;
        }
    }

    // Nested Types elided (fastfunc for the realf)

}

The $Foo class does the actual initialization:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
internal static class $Foo
{
    // Fields
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    internal static readonly Unit f@5;
    [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
    internal static int init@;

    // Methods
    static $Foo()
    {
        Unit local1 = f@5 = ExtraTopLevelOperators.printfn<Unit>(new PrintfFormat<Unit, TextWriter, Unit, Unit, Unit>("Called f"));
        ExtraTopLevelOperators.printfn<Unit>(new PrintfFormat<Unit, TextWriter, Unit, Unit, Unit>("Foo init"));
    }
}

Some conclusions:

  • Module initialization code is order-dependent - it matters where you put your do's and lets (but I believe this has always been the case)
  • Initialization code for a module will _only_ be run if you access a let constant from that module, _not_ before you call a function (or type?) of that module (since calling a function does not even access the initializtion class $Foo). I am quite sure this is a (confusing!) change.

I think that last thing bit us: calling a type's constructor or function in a module does not run the initialization in the module at all.I'm going to send all this to fsbugs as well, I think. It should get some attention.

By on 5/24/2009 4:50 AM ()

Thanks a lot for the explanation, Kurt. I'd guess the new behaviour is not a bug, only something that was not mentioned in the release notes, maybe because the details of the previous behaviour were "implemention-defined". I actually like the new implementation because a) it seems to be more efficient and b) it gives more control over when the initialization code is actually run. With the new semantics it seems easier to delay initialization until one really needs it.

By on 5/24/2009 5:14 AM ()

For "posteriority", this is what Don Syme had to say about it (don't think he'll mind that I post this here...)"F# Beta1 uses CIL "beforefieldinit" initialization triggers. That means initialization is triggered when a field is accessed in the compiled storage associated with a module. This is covered in the F# specification on order of initialization.

Initialization behaviour will always be at least as strict as this, i.e. initialization can always be forced by accessing a public field (i.e. non-function, non-generic value) of a module. So in this sense you can rely on it.

However, we are actively looking at forcing initialization earlier in Beta2, i.e. moving to use "beforeany" initialization."With that in mind, I'm going to try to sort some things out so that they're future-proof.And this is indeed covered in the language-spec (that'll teach me searching for something before it is fully loaded...) at [link:research.microsoft.com]

By on 5/24/2009 11:48 AM ()

Thanks Kurt, glad to be of help

One minor addition: we did fix a bug here, where initialization was being forced needlessly when accessing a discriminated union. Indeed in certain circumstances this led to incorrect behavior.

I suspect this is what led to the change you observed.

Kind regards

don

By on 5/24/2009 2:52 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper