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 }
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:
Post a Comment