This entry is part of "The Basic Syntax of F#" series:
- The basic syntax of F# – keywords and constructs
- The basic syntax of F# – classes, interfaces, and members
- The basic syntax of F# – types
Today’s blog entry covers the F# syntax for authoring classes, interfaces, and members. Together with the previous blog entry, this probably covers the most common 90% or so of the language. Again today I will intentionally err on the side of simplicity, at the expense of total accuracy.
Defining F# types
There are lots of ways to define new types and type names in F#, and most involve declarations with the "type" keyword. Today I’ll focus on just classes and interfaces; in a future blog entry I’ll describe the syntax for the rest of the kinds of types (records, discriminated unions, enums, type abbreviations, units of measure, structs, delegates, exceptions, and modules).
Classes (sans interfaces or inheritance)
Here is an example skeleton class definition whose only purpose is to demonstrate most of the language syntax involved in defining classes:
static let PI = 3.14
static do printfn "static constructor"
let mutable z = x + y
do printfn "%s" (this.ToString()) (*5*)
printfn "more constructor effects"
internal new (a) = MyClass(a,a)
static member StaticProp = PI
static member StaticMethod a = a + 1
member internal self.Prop1 = x
member self.Prop2 with get() = z
and set(a) = z <- a
member self.Method(a,b) = x + y + z + a + b
I’ll discuss each part in turn; the commented numbers are to help call out specific bits in the prose that follows.
A class definition usually starts like this:
but there are lots of optional bits that can go before the ‘=’ when defining a class. Back in the main example,
the ‘public’ before (*1*) is the accessibility of the class type itself; new types default to being public. After the name of the class, if the class is generic, you specify the generic parameters in angle brackets (MyClass<’a> is a class with one generic parameter; the generic parameter is unused in this example, it just demonstrates the syntax). The next portion defines the so-called "implicit constructor" (whose meaning is described in more detail below). The ‘public’ before (*2*) is the accessibility of the implicit constructor (public is the default) and the stuff inside parentheses before (*3*) are the arguments to the implicit constructor. The "as this" before (*4*) is an optional way to name the current object (you can use any identifier you like, it is lexically scoped to all non-static portions of the class body); the only time you need it is when referring to "this" inside the implicit constructor body (such as on the line marked (*5*) in the main example).
The body of the class conceptually has two portions, ‘let’s and ‘do’s (which run as part of construction), and ‘member’s. Both portions can have ‘static’ and instance (non-static) parts; non-static is the default. Names bound by ‘let’s are lexically scoped in the class body (and thus are always "private" to the class). The non-static ‘let’s and ‘do’s run as the body of the implicit constructor (.ctor), the static ‘let’s and ‘do’s comprise the type’s static constructor (.cctor). In other words, this code
static do printfn "static constructor"
runs when something first refers to the enclosing MyClass type, and this code
do printfn "%s" (this.ToString()) (*5*)
printfn "more constructor effects"
runs whenever you create an instance of MyClass. (As with other .NET languages, it is rare to define a static constructor.)
Members are the public interface to functionality in a class, and come in two main flavors: properties and methods. Members without arguments are "getter" properties; members with arguments are methods. From the example:
static member StaticMethod a = a + 1
member internal self.Prop1 = x
member self.Prop2 with get() = z
and set(a) = z <- a
member self.Method(a,b) = x + y + z + a + b
Members can be static or not (non-static is the default). Non-static (instance) members must declare a self-identifier. Just as with the optional "as this" from the class declaration, you can use any identifier you like; the identifier is bound to the current object and scoped to the member’s body (in these examples I chose "self"). Members default to being public (though "Prop1" demonstrates that you can add an optional accessibility specifier). If you want to define a property with a "setter" as well as a "getter", you can use the syntax as in "Prop2".
To define constructors overloads other than the implicit constructor, use a syntax like this:
The accessibility specifier is optional (defaults to public, just like other members). The "body" of the constructor must "call forward" to another constructor (eventually ending up with a call to the implicit constructor). Pragmatically, this means your implicit constructor must initialize everything, and thus probably takes the most arguments of any constructor overload.
There are a number of features of members I am omitting for simplicity, including named and optional parameters, overloading, events, and fields. You can read the language spec for more details on these features, or ask questions as a blog comment.
Interfaces
Defining and using interfaces in F# is very straightforward. An interface is a type that only defines abstract members and/or inherits other interfaces. Here are a couple examples:
abstract member Foo : int -> int
type IQuxable =
abstract member Qux : unit -> string
inherit IFooable
"IFooable" is an interface with a method "Foo" that takes an int and returns an int. "IQuxable" is an interface that inherits "IFooable" and adds another method named "Qux".
To have a class implement an interface, just add an ‘interface…with’ declaration to the end of the class body for each interface you want to implement:
member this.SomeMethod() = printfn "hi"
interface IQuxable with
member this.Foo x = x + 1
member this.Qux() = "qux!"
Here "FooQux" is a class containing a method "SomeMethod" and implementing the "IQuxable" interface.
Note that in F#, when a class implements an interface, the interface implementation is always explicit, which means you need an explicit upcast to call interface methods. For example
fooQux.SomeMethod()
let x = (fooQux :> IFooable).Foo 42 // must upcast to call "Foo"
Abstract classes and "virtual" methods
Here is an example of an abstract class:
type SomeBase() =
member this.ConcreteMethod y = y + 1
abstract member AbstractMethod : int -> int
abstract member VirtualMethod : int -> int
default this.VirtualMethod x = x + 1
An abstract class is like a (normal, concrete) class, except it contains some abstract (not implemented) members that a subclass must implement. What would be a "virtual" method in C# is expressed as an ‘abstract’ method that has a ‘default’ implementation. An abstract class type must be marked with the "AbstractClass" attribute.
Inheritance
With inheritance come three more F# keywords: ‘inherit’, ‘override’, and ‘base’:
inherit SomeBase()
override this.AbstractMethod z = z + 1
override this.VirtualMethod x =
1 + base.VirtualMethod x
member this.OtherMethod() = ()
The ‘inherit’ clause defines the base class of a class, and also calls the base constructor as part of the implicit constructor. (That is, in this example the zero-argument implicit constructor of "SomeDerived" calls the zero-argument constructor of "SomeBase".) Abstract members that have no implementation must be overridden by the derived class (or else the derived class can also be marked abstract). Abstract methods with default implementations can be overridden; to call the inherited method, use the ‘base’ keyword.
Other miscellany
I have intentionally omitted many details to keep this blog entry reasonably short (but still cover all the major common bits of syntax). Here are a few notable omissions:
- You can enclose a class body between the tokens ‘class’ and ‘end’. You can enclose an interface body between the tokens ‘interface’ and ‘end’. F# normally infers the kind of type being defined, making these tokens unnecessary/redundant.
- As usual, whitespace matters; though I showed most method/property bodies on a single line of code, more commonly you will have a newline after the ‘=’ and indent the body underneath the declaration, as with "VirtualMethod" in "SomeDerived".
- I only talked about accessibility (e.g. ‘public’, ‘internal’) and properties (rather than methods) in the first section; you can hopefully extrapolate how to define e.g. an internal virtual property.