Declaring Class Constructors in F# 2.0, also for Singletons
In F#, class constructors can be defined in different ways. For a non-static class, one usually declares a primary constructor plus zero or more additional constructors. For a static class, one usually declares a module.
In F#, it is possible to create a thread-safe singleton with just three lines of code:
/// A public thread-safe singleton class in F#,
/// using a private primary constructor.
type Singleton private() =
static let instance = Singleton()
static member Instance = instance
This would be the equivalent code in C#:
public class Singleton{
static readonly Singleton instance = new Singleton();
Singleton() {}
public static Singleton Instance {get { return instance;}}
}
In the above F# example, private()
declares an empty primary constructor. Using a primary constructor is the recommended practice for non-static classes in F#. According to the F# 2.0 Language Specification, § 8.6.4., any additional constructors must call the primary constructor (either directly or indirectly), as in the following example:
/// A public Person class in F#, using a public primary
/// constructor and an additional public constructor.
type Person(name: string, guid) =
new(name, guid) = Person(name, Guid.Parse(guid))
member x.Guid = guid
member x.Name = name
This would be the equivalent code in C#:
public class Person{
readonly string name;
readonly Guid guid;
public Person(string name, Guid guid){
this.name = name;
this.guid = guid;
}
public Person(string name, string guid):
this(name, Guid.Parse(guid)){
}
public Guid Guid {get {return guid;}}
public string Name {get {return name;}}
}
It is also possible to define classes in F# with only additional constructors, but no primary constructor. However, this restricts the kind of syntax available for the rest of the class. Primary constructors should only be avoided when there is a strong reason, e.g., to simplify automatic code generation. The following modified Singleton example shows some of the difficulties one may face when avoiding a primary constructor:
/// This type is intended for private use within Singleton2 only.
type private SyncRoot = private new() = {}
/// A singleton with no primary constructor.
type Singleton2 =
// As we cannot use implicit ("let"-bound) fields, we have to
// use explicit ("val"-bound) fields. However, explicit fields
// who are static must also be default-initialized, mutable, and private.
[<DefaultValue>]
static val mutable private instance: Singleton2
private new() = {}
// As we cannot rely on the inherent thread safety of non-mutable
// let-bound fields, we have to apply a lock.
static member Instance =
lock typeof<SyncRoot> (fun() ->
if Unchecked.compare Singleton2.instance Unchecked.defaultof<Singleton2> = 0
then Singleton2.instance <- Singleton2())
Singleton2.instance
What happens if you define a class in F# with no constructors? Unlike C#, F# will not generate a default public parameterless instance constructor in this case. The F# class simply cannot be instantiated. Oddly enough, while the compiler forbids the declaration of static fields via let bindings in this scenario (which would be useful), it does not complain if you define instance-related properties/methods/val
bindings (which are useless, because they can never be accessed). This leaves you with only static properties/methods/val
fields, which is quite restrictive. Furthermore, even if you decide to declare such a kind of type with only static accessibility, it will still not appear as a static class to its users. For these reasons, it is best to never define classes without constructors in F#. If you need what in C# we call a static class
, you better define a module
in F#:
/// A module (static class) defined in F#. Type-level let bindings are public by default,
/// but can also be internal or private. This is opposed to type (non-static class)
/// definitions, where type-level let bindings are always inherently private.
module PersonFactory =
let CreatePerson(name, guid: Guid) = new (name, guid)
This would be the equivalent code in C#:
public static class PersonFactory{
public static Person CreatePerson(string name, Guid guid){
return new Person(name, guid);
}
}
However, using modules in F# 2.0 also has a disadvantage: Method overloading is not possible. If you need method overloading in a static class, there is no other way than declaring a non-static type with just a private parameterless constructor, or no constructor at all:
/// A class with overloaded members in F#.
type PersonFactory2 =
static member CreatePerson(name, guid: Guid) = Person(name, guid)
static member CreatePerson(name, guid: string) = Person(name, guid)
This would be the equivalent code in C#:
public class PersonFactory2{
public static Person CreatePerson(string name, Guid guid){
return new Person(name, guid);
}
public static Person CreatePerson(string name, string guid){
return new Person(name, guid);
}
}
There is no static constructor method in F#. In order to run static constructor code in F#, you first need to declare a primary constructor (which is a kind of instance constructor). Once a primary constructor exists, any type-level static let
and do
-bound code implicitly becomes static constructor code.
/// Running "static constructor code" in F#.
type SomeType() =
static let instanceCount = ref 0
static do printfn "Type created at %A" DateTime.Now
do printfn "Instance created at %A" DateTime.Now
do incr instanceCount
static member InstanceCount = !instanceCount
This would be equivalent code in C#:
public class SomeType{
static SomeType(){
Console.WriteLine(
string.Format("Type created at {0:G}", DateTime.Now));
}
public SomeType(){
Console.WriteLine(
string.Format("Instance created at {0:G}", DateTime.Now));
InstanceCount++;
}
public static int InstanceCount {get; private set;}
}
In this blog post, I have covered the basics of declaring F# constructor code for class types and modules. Much more could be said with regards to type construction in F#. For instance, I did not cover structures, records, object expressions, calling base class constructors and the details of read-only fields versus mutable
fields versus reference value fields. Further information can be found in the F# specification, F# Language Reference, F# guide or in one of the available F# books such as Programming F#.