Wednesday, February 18, 2009

[2009.02.18] F# Types: Classes [II/III]

The next type we will examine is a class, which is one of the most commonly used containers in the object oriented world. Like records, classes (and structs) provide all the features previously examined, such as methods, properties, indexers, interfaces, events, etc. The one thing that stands out the most is how a class is constructed in F#. This can be done either using explicit or implicit constructor notation.

Explicit Constructor Notation

This is actually the conventional way to create a class - define private variables, then expose public properties to access and modify the private variables in a safe way and all other interactions with the members of the class go through these properties. In C# you can simply define a class and it will compile with no problems, because a default constructor is automatically created for you even if you did not explicitly do it yourself. Therefore, you can new up an instance of the class and keep chugging away. In F#, you must define at least a default constructor for your class or it will not compile.

Take a look at the simple class definition in C#. If you look at the executable under ILDasm.exe you will see the constructor created for you.

    1 using System;

    2 

    3 namespace ConsoleApplication_TestClass

    4 {

    5     class Complex

    6     {

    7         static void Main(string[] args)

    8         {

    9         }

   10     }

   11 }


[2009.02.18].01.Class.Type.CSharp.ILDasm
Fig.1 ILDasm.exe for the Complex class in C#

In F#, this means you will have to take an extra step but follow the same process to create and use an object. For instance, if we asumed that a default constructor would be provided, an error is thrown by the compiler as shown in the code.

    1 #light

    2 #r "FSharp.PowerPack.dll"

    3 

    4 type MyComplex =

    5     class

    6         // define private variables

    7         val private re : float

    8         val private im : float   

    9 

   10         // define public properties

   11         //  to access/use the variables

   12         member c.Re = c.re

   13         member c.Im = c.im

   14     end

   15 

   16 // if we try to use this class as it,

   17 //  will generate an error

   18 let c = MyComplex(1.,1.)

The following compile time error is thrown:
error FS0039: The value or constructor 'MyComplex' is not defined.

This is fixed by simply explicitly adding a constructor to initialize the necessary fields. The listing below shows the necessary code needed to get this to work. The downside to this approach is that it is a bit more verbose than we would like, given one of the cool functionality of F# is its compactness or succinctness.

    1 #light

    2 #r "FSharp.PowerPack.dll"

    3 

    4 type MyComplex =

    5     class

    6         // define private variables

    7         val private re : float

    8         val private im : float   

    9 

   10         // define public properties

   11         //  to access/use the variables

   12         member c.Re = c.re

   13         member c.Im = c.im

   14 

   15         // add constructor

   16         new(r, i) = {re = r; im = i}

   17     end

   18 

   19 // now this works

   20 let c = MyComplex(1.,1.)

   21 > val it : MyComplex =

   22         MyComplex { Im = 1.0; Re = 1.0;}

Implicit Constructor Notation

We can move closer to the functional programming paradigm of compactness using implicit constructors. In general, we only need one constructor to initialize a class to a good state. To implement this form of constructors, basically put all the arguments needed for the construction of the class in good state in parenthesis following the name of the class, then adding the necessary public properties to access these fields/constructor arguments. I find this comparable to the notion of Automatic Properties introduced in C# 3.0. Here is what the new syntax would look like with this approach.

    1 #light

    2 #r "FSharp.PowerPack.dll"

    3 

    4 type MyComplex(r: float, i: float) =

    5     class

    6 

    7         // define public properties to use

    8         //  the constructor arguments

    9         member c.Re = r

   10         member c.Im = i

   11     end

   12 

   13 

   14 // the result when the above code is executed

   15 // note the "new" keyword being used to indicate

   16 //  implicit constructor notation

   17 > type MyComplex =

   18   class

   19     new : r:float * i:float -> MyComplex

   20     member Im : float

   21     member Re : float

   22   end

Another consequence of this approach is that you can use a series of let bindings before the member definitions to define the construction sequence of the class. This is just a trivial example but you can extend it to your own implementation.

    1 type MyComplex(r: float, i: float) =

    2     class

    3         // class construction sequence

    4         // use let bindings to initialize the

    5         //  various aspects of the class

    6         let real = float r

    7         let imag = float i

    8 

    9         // define public properties to use

   10         //  the constructor arguments

   11         member c.Re = real

   12         member c.Im = imag

   13     end

Again, this type of approach provides only one constructor for use.

No comments: