Classes
Last updated
Was this helpful?
Last updated
Was this helpful?
This post and the next will cover the basics of creating and using classes and methods in F#.
Just like all other data types in F#, class definitions start with the type
keyword.
The thing that distinguishes them from other types is that classes always have some parameters passed in when they are created -- the constructor -- and so there are always parentheses after the class name.
Also, unlike other types, classes must have functions attached to them as members. This post will explain how you do this for classes, but for a general discussion of attaching functions to other types see .
So, for example, if we want to have a class called CustomerName
that requires three parameters to construct it, it would be written like this:
Let's compare this with the C# equivalent:
You can see that in the F# version, the primary constructor is embedded into the class declaration itself --- it is not a separate method. That is, the class declaration has the same parameters as the constructor, and the parameters automatically become immutable private fields that store the original values that were passed in.
So in the above example, because we declared the CustomerName
class as:
therefore firstName
, middleInitial
, and lastName
automatically became immutable private fields.
You might not have noticed, but the CustomerName
class defined above does not constrain the parameters to be strings, unlike the C# version. In general, type inference from usage will probably force the values to be strings, but if you do need to specify the types explicitly, you can do so in the usual way with a colon followed by the type name.
Here's a version of the class with explicit types in the constructor:
One little quirk about F# is that if you ever need to pass a tuple as a parameter to a constructor, you will have to annotate it explicitly, because the call to the constructor will look identical:
The example class above has three read-only instance properties. In F#, both properties and methods use the member
keyword.
Also, in the example above, you see the word "this
" in front of each member name. This is a "self-identifier" that can be used to refer to the current instance of the class. Every non-static member must have a self-identifier, even it is not used (as in the properties above). There is no requirement to use a particular word, just as long as it is consistent. You could use "this" or "self" or "me" or any other word that commonly indicates a self reference.
When a class is compiled (or when you over hover the definition in the editor), you see the "class signature" for the class. For example, for the class definition:
the corresponding signature is:
The class signature contains the signatures for all the constructors, methods and properties in the class. It is worth understanding what these signatures mean, because, just as with functions, you can understand what the class does by looking at them. It is also important because you will need to write these signatures when creating abstract methods and interfaces.
So in this case, the method signature is:
And for comparison, the corresponding signature for a standalone function would be:
Constructor signatures are always called new
, but other than that, they look like a method signature.
Constructor signatures always take tuple values as their only parameter. In this case the tuple type is int * string
, as you would expect. The return type is the class itself, again as you would expect.
Again, we can compare the constructor signature with a similar standalone function:
Finally, property signatures such as member Two : int
are very similar to the signatures for standalone simple values, except that no explicit value is given.
After the class declaration, you can optionally have a set of "let" bindings, typically used for defining private fields and functions.
Here's some sample code to demonstrate this:
In the example above, there are three let bindings:
privateValue
is set to the initial seed plus 1
mutableValue
is set to 42
The privateAddToSeed
function uses the initial seed plus a parameter
Because they are let bindings, they are automatically private, so to access them externally, there must be a public member to act as a wrapper.
Note that the seed
value passed into the constructor is also available as a private field, just like the let-bound values.
Sometimes, you want a parameter passed to the constructor to be mutable. You cannot specify this in the parameter itself, so the standard technique is to create a mutable let-bound value and assign it from the parameter, as shown below:
In cases, like this, it is quite common to give the mutable value the same name as the parameter itself, like this:
In the CustomerName
example earlier, the constructor just allowed some values to be passed in but didn't do anything else. However, in some cases, you might need to execute some code as part of the constructor. This is done using do
blocks.
Here's an example:
The "do" code can also call any let-bound functions defined before it, as shown in this example:
One of the differences between the "do" and "let" bindings is that the "do" bindings can access the instance while "let" bindings cannot. This is because "let" bindings are actually evaluated before the constructor itself (similar to field initializers in C#), so the instance in a sense does not exist yet.
If you need to call members of the instance from a "do" block, you need some way to refer to the instance itself. This is again done using a "self-identifier", but this time it is attached to the class declaration itself.
In general though, it is not best practice to call members from constructors unless you have to (e.g. calling a virtual method). Better to call private let-bound functions, and if necessary, have the public members call those same private functions.
A method definition is very like a function definition, except that it has the member
keyword and the self-identifier instead of just the let
keyword.
Here are some examples:
Unlike normal functions, methods with more than one parameter can be defined in two different ways:
The tuple form, where all the parameters as passed in at the same time, comma-separated, in a single tuple.
The curried approach is more functional, and the tuple approach is more object-oriented. Here is an example class with a method for each approach:
So which approach should you use?
The advantages of tuple form are:
Compatible with other .NET code
Supports named parameters and optional parameters
Supports method overloads (multiple methods with the same name that differ only in their function signature)
On the other hand, the disadvantages of tuple form are:
Doesn't support partial application
Doesn't work well with higher order functions
Doesn't work well with type inference
A common pattern is to create let-bound functions that do all the heavy lifting, and then have the public methods call these internal functions directly. This has the benefit that the type inference works much better with functional-style code than with methods.
Here's an example:
Unlike normal let-bound functions, methods that are recursive do not need the special rec
keyword. Here's the boringly familiar Fibonacci function as a method:
As usual, the types for a method's parameters and return value can normally be inferred by the compiler, but if you need to specify them, you do so in the same way that you would for a standard function:
Properties can be divided into three groups:
Immutable properties, where there is a "get" but no "set".
Mutable properties, where there is a "get" and also a (possibly private) "set".
Write-only properties, where there is a "set" but no "get". These are so unusual that I won't discuss them here, but the MSDN documentation describes the syntax if you ever need it.
The syntax for immutable and mutable properties is slightly different.
For immutable properties, the syntax is simple. There is a "get" member that is similar to a standard "let" value binding. The expression on the right-hand side of the binding can be any standard expression, typically a combination of the constructor parameters, private let-bound fields, and private functions.
Here's an example:
For mutable properties however, the syntax is more complicated. You need to provide two functions, one to get and one to set. This is done by using the syntax:
Here's an example:
To make the set function private, use the keywords private set
instead.
Starting in VS2012, F# supports automatic properties, which remove the requirement to create a separate backing store for them.
To create an immutable auto property, use the syntax:
To create a mutable auto property, use the syntax:
Note that in this syntax there is a new keyword val
and the self-identifier has gone.
Here's a complete example that demonstrates all the property types:
At this point you might be confused by the difference between properties and parameterless methods. They look identical at first glance, but there is a subtle difference -- "parameterless" methods are not really parameterless; they always have a unit parameter.
Here's an example of the difference in both definition and usage:
You can also tell the difference by looking at the signature of the class definition
The class definition looks like this:
The method has signature MyFunc : unit -> int
and the property has signature MyProp : int
.
This is very similar to what the signatures would be if the function and property were declared standalone, outside of any class:
The signatures for these would look like:
which is almost exactly the same.
In addition to the primary constructor embedded in its declaration, a class can have additional constructors. These are indicated by the new
keyword and must call the primary constructor as their last expression.
Just as in C#, classes can have static members, and this is indicated with the static
keyword. The static
modifier comes before the member keyword.
Members which are static cannot have a self-identifier such as "this" because there is no instance for them to refer to.
There is no direct equivalent of a static constructor in F#, but you can create static let-bound values and static do-blocks that are executed when the class is first used.
You can control the accessibility of a member with the standard .NET keywords public
, private
and internal
. The accessibility modifiers come after the member
keyword and before the member name.
Unlike C#, all class members are public by default, not private. This includes both properties and methods. However, non-members (e.g. let declarations) are private and cannot be made public.
Here's an example:
For properties, if the set and get have different accessibilities, you can tag each part with a separate accessibility modifier.
In practice, the "public get, private set" combination that is so common in C# is not generally needed in F#, because immutable properties can be defined more elegantly, as described earlier.
If you are defining classes that need to interop with other .NET code, do not define them inside a module! Define them in a namespace instead, outside of any module.
The reason for this is that F# modules are exposed as static classes, and any F# classes defined inside a module are then defined as nested classes within the static class, which can mess up your interop. For example, some unit test runners don't like static classes.
Here's an example of two F# classes, one defined outside a module and one defined inside:
And here's how the same code might look in C#:
Now that we have defined the class, how do we go about using it?
One way to create an instance of a class is straightfoward and just like C# -- use the new
keyword and pass in the arguments to the constructor.
However, in F#, the constructor is considered to be just another function, so you can normally eliminate the new
and call the constructor function on its own, like this:
In the case when you are creating a class that implements IDisposible
, you will get a compiler warning if you do not use new
.
And once you have an instance, you can "dot into" the instance and use any methods and properties in the standard way.
We have seen many examples of member usage in the above discussion, and there's not too much to say about it.
Remember that, as discussed above, tuple-style methods and curried-style methods can be called in distinct ways:
Method signatures such as are very similar to the , except that the parameter names are part of the signature itself.
You can see that, just like normal functions, methods can have parameters, call other methods, and be parameterless (or to be precise, take a )
The curried form, where parameters are separated with spaces, and partial application is supported. (Why "curried"? See the .)
For a more detailed discussion on tuple form vs. curried form see the post on .
For more details on how to do this, see .
If you are unclear on the difference and why the unit parameter is needed for the function, please read the .
F# classes which are defined outside a module are generated as normal top-level .NET classes, which is probably what you want. But remember that (as discussed in a ) if you don't declare a namespace specifically, your class will be placed in an automatically generated module, and will be nested without your knowledge.
This can be a useful reminder to use the use
keyword instead of the let
keyword for disposables. See for more.