We now start the OO leg of our F# journey, where we will look at how to create classes (generic classes too) and use OO things like inheritance/interfaces, and we shall also look at how to use events within our classes, and how to use reflection to do meta programming against our classes instances.
Creating Types
We have already seen a few example of types being created on our journey so far, is may have passed you by which is fine, so here are a few ground rules when creating custom types
- Types MUST live in a module
- Types that do not contain any constructors/members at all, that are just marker classes MUST use the class/end class syntax
Let’s have a look at these 2 points in a bit more detail shall we.
So for point 1, what does that really mean? So if I have some code like the example shown below, is it valid? The answer is no….And why is the answer no? Well it is due to the fact the Person type is declared within the main method, which is not valid. As previously stated a type must be declared in a module, so to fix this we simply need to move the type to a dedicated module. That would fix point 1.
The next point from the above list means that you are not really allowed to do this, this is not a valid type definition, as it doesn’t contain any members
This is easily rectified using the class/end class syntax, which can be used just like this. Remember you ONLY need this if you want to create some sort of marker class, I don’t know how often you would like to do this, but if you do this is how you would do it
Constructors
In F# there is always a primary constructor which follows the types name which takes the arguments that are used to create an instance of the type. The primary constructor may contain let and do bindings, which according to MSDN work this way:
The let and do bindings in a class definition form the body of the primary class constructor, and therefore they run whenever a class instance is created. If a let binding is a function, then it is compiled into a member. If the let binding is a value that is not used in any function or member, then it is compiled into a variable that is local to the constructor. Otherwise, it is compiled into a field of the class. The do expressions that follow are compiled into the primary constructor and execute initialization code for every instance. Because any additional constructors always call the primary constructor, the let bindings and do bindings always execute regardless of which constructor is called.
Fields that are created by let bindings can be accessed throughout the methods and properties of the class; however, they cannot be accessed from static methods, even if the static methods take an instance variable as a parameter. They cannot be accessed by using the self identifier, if one exists.
http://msdn.microsoft.com/en-us/library/dd233205.aspx
Types may also have other constructors that MUST call the primary constructor, this is easily done using the new keyword which may be an empty constructor, or may take arguments.
Here is an example of a type that has 3 constructors
type public Person(firstName : string, lastName : string) =
let fullName = String.Format("{0} {1}",firstName, lastName)
new() = Person("", "")
new(firstName : string) = Person(firstName, "")
member public this.FirstName = firstName
member public this.LastName = lastName
member public this.FullName = fullName
Which can be used as follows:
let p1 = new Person("sacha","Barber")
let p2 = Person("sacha","Barber")
let p3 = new Person()
let p4 = new Person("Steve")
do printfn "p1 = FirstName=%A, LastName=%A, FullName=%A" p1.FirstName p1.LastName p1.FullName
do printfn "p2 = FirstName=%A, LastName=%A, FullName=%A" p2.FirstName p2.LastName p2.FullName
do printfn "p3 = FirstName=%A, LastName=%A, FullName=%A" p3.FirstName p3.LastName p3.FullName
do printfn "p4 = FirstName=%A, LastName=%A, FullName=%A" p4.FirstName p4.LastName p4.FullName
Which when run gives the following output
Dependant Types
In one of the posts we already covered we saw how to deal with dependant types, that is Type A depends on Type B, and Type B depends on Type A. Here is an example of the problem:
In order to do that we can just loose the dependant type keyword, and replace it with the and keyword, which if you recall was done like this:
type Person(manager :Manager) =
member this.Manager = manager
and Manager(name:string) =
let underlings = new List()
member this.Underlings = underlings
For more information on this particular subject you can read the other post :
sachabarbs.wordpress.com/2014/04/09/f-15-code-organization-modules-files-types/
Adding Members
To add members to a type you just need use the self identifier followed by the period then a name. Members belong to an instance, not the type. Here is a trivial example of a “PrettyPrint
” method, which has been added to our on going example
type public Person(firstName : string, lastName : string) =
let fullName = String.Format("{0} {1}",firstName, lastName)
new() = Person("", "")
new(firstName : string) = Person(firstName, "")
member public this.FirstName = firstName
member public this.LastName = lastName
member public this.FullName = fullName
member this.PrettyPrint() =
printfn "FirstName %s, LastName %s" this.FirstName this.LastName |> ignore
Which we can use like this:
let p1 = new Person("sacha","Barber")
printfn "let p1 = new Person(\"sacha\",\"Barber\")"
printfn "p1.PrintyPrint()"
do p1.PrettyPrint()
Which gives us this result:
Static Members
You may also add static members to a class which is done as follows:
Just like in any other .NET language a static member belongs to the type and not the instance, so to use it we simply use the type name followed by the member name. Here is the same demo class we have been building up, with a static “LastNameFormat
” method which returns a tuple of the passed in firstName
and LastName
reversed:
type public Person(firstName : string, lastName : string) =
let fullName = String.Format("{0} {1}",firstName, lastName)
new() = Person("", "")
new(firstName : string) = Person(firstName, "")
member public this.FirstName = firstName
member public this.LastName = lastName
member public this.FullName = fullName
member this.PrettyPrint() =
printfn "FirstName %s, LastName %s" this.FirstName this.LastName |> ignore
static member LastNameFormat(f: string, l: string) = (l,f)
Which we make use of like this:
let p1 = new Person("sacha","Barber")
printfn "let p1 = new Person(\"sacha\",\"Barber\")"
printfn "Person.LastNameFormat = %A" (Person.LastNameFormat(p1.FirstName, p1.LastName))
Which gives this result:
Adding Properties
There are 2 main areas that we need to cover when talking about properties:
- Get only properties
- Get and (possibly private) set
For immutable properties the syntax is simple we can simply do something like this:
type foo(name) =
member this.Name = name
For mutable properties it gets a little more complex, where we have the following sort of syntax:
type foo(name) =
let mutable myName = name
member this.MyName
with get() = myName
and set(value) = myName <- value
TIP : To make a set private you can use “private set
” instead
From VS2012 onwards F# supports auto properties, which you can use to simplify your code, here are some examples of how you can use that.
type foo(name: string, age:int) =
member val MyName = name
member val Age = age with get, set
Access Modifiers
In F# you do not have the full gamut of access modifiers you may be used to if you are coming from C#, you are limited to:
public
: indicates the entity can be accessed by all callers
internal
: indicates the entity can be accessed only by the same assembly
private
: indicates the entity can be accessed only from the enclosing type of module
Access modifiers can be applied to modules, types, methods, value definitions, functions, properties and explicit fields
- Access modifiers are generally put in front of the name of the entity. If no access modifier is used the default is
public
except in the case of let
bindings in a type, which are always private
to the type.