Object Types
Object oriented constructs in F# are compatible with the OO support in .NET CLR, which implies that F# supports single implementation inheritance (a class can have one base class), multiple interface inheritance (a class can implement several interfaces and an interface can inherit from multiple interfaces), subtyping (an inherited class can be casted to the base class type) and dynamic type tests (it is possible to test whether a value is a value of an inherited class casted to a base type). Finally, all object types share a common base class which is called obj in F# and is an alias to the CLR type System.Object.
F# object types can have fields, constructors, methods and properties (a property is just a syntactic sugar for getter and setter methods). The following example introduces the F# syntax for object types:
type MyCell(n:int) =
let mutable data = n + 1
do printf "Creating MyCell(%d)" n
member x.Data
with get() = data
and set(v) = data <- v
member x.Print() =
printf "Data: %d" data
override x.ToString() =
sprintf "(Data: %d)" data
static member FromInt(n) =
MyCell(n)
Type MyCell has a mutable field called data, a property called Data, an instance method Print, a static method FromInt and the type also contains one overridden method called ToString, which is inherited from the obj type and returns a string representation of the object. Finally, the type has an implicit constructor. Implicit constructors are syntactical feature which allows us to place the constructor code directly inside the type declaration and to write the constructor arguments as part of the type construct. In our example, the constructor initializes the mutable field and prints a string as a side effect. F# also supports explicit constructors that have similar syntax as other members, but these are needed rarely.
In the previous example we implemented a concrete object type (a class), which means that it is possible to create an instance of the type and call its methods in the code. In the next example we will look at declaration of an interface (called abstract object type in F#). As you can see, it is similar to the declaration of a class:
type AnyCell =
abstract Value : int with get, set
abstract Print : unit -> unit
The interesting concept in the F# object oriented support is that it is not needed to explicitly specify whether the object type is abstract (interface), concrete (class) or partially implemented (class with abstract methods), because the F# complier infers this automatically depending on the members of the type. Abstract object types (interfaces) can be implemented by a concrete object type (class) or by an object expression, which will be discussed shortly. When implementing an interface in an object type we use the interface .. with construct and define the members required by the interface. Note that the indentation is significant in the lightweight F# syntax, meaning that the members implementing the interface type have to be indented further:
type ImplementCell(n:int) =
let mutable data = n + 1
interface AnyCell with
member x.Print() = printf "Data: %d" data
member x.Value
with get() = data
and set(v) = data <- v
The type casts supported by F# are upcast, used for casting an object to a base type or to an implemented interface type (written as o :> TargetType), downcast, used for casting back from a base type (written as o :?> TargetType), which throws an exception when the value isn’t a value of the specified type and finally, a dynamic type test (written as o :? TargetType), which tests whether a value can be casted to a specified type.
Object expressions
As already mentioned, abstract types can be also implemented by an object expression. This allows us to implement an abstract type without creating a concrete type and it is particularly useful when you need to return an implementation of a certain interface from afunction or build an implementation on the fly using functions already defined somewhere else in your program. The following example implements the AnyCell type:
> let newCell n =
let data = ref n
{ new AnyCell with
member x.Print() = printf "Data: %d" (!data)
member x.Value
with get() = !data
and set(v) = data:=v };;
val newCell : int -> AnyCell
In this code we created a function that takes an initial value as an argument and returns a cell holding this value. In this example we use one more type of mutable values available in F#, called reference cell, which are similar to a mutable values, but more flexible (the F# compiler doesn’t allow using an ordinary mutable value in this example). A mutable cell is created by a ref function taking an initial value. The value is accessed using a prefix ! operator and can be modified using := operator. When implementing the abstract type, we use a new ... with construct with members implementing the functionality required by the abstract type (an object expression can’t add any members). In this example we need a reference cell to hold the value, so the cell is declared in a function and captured in a closure, which means that it will exist until the returned object will be garbage collected.
Object oriented constructs in F# are compatible with the OO support in .NET CLR, which implies that F# supports single implementation inheritance (a class can have one base class), multiple interface inheritance (a class can implement several interfaces and an interface can inherit from multiple interfaces), subtyping (an inherited class can be casted to the base class type) and dynamic type tests (it is possible to test whether a value is a value of an inherited class casted to a base type). Finally, all object types share a common base class which is called obj in F# and is an alias to the CLR type System.Object.
F# object types can have fields, constructors, methods and properties (a property is just a syntactic sugar for getter and setter methods). The following example introduces the F# syntax for object types:
type MyCell(n:int) =
let mutable data = n + 1
do printf "Creating MyCell(%d)" n
member x.Data
with get() = data
and set(v) = data <- v
member x.Print() =
printf "Data: %d" data
override x.ToString() =
sprintf "(Data: %d)" data
static member FromInt(n) =
MyCell(n)
Type MyCell has a mutable field called data, a property called Data, an instance method Print, a static method FromInt and the type also contains one overridden method called ToString, which is inherited from the obj type and returns a string representation of the object. Finally, the type has an implicit constructor. Implicit constructors are syntactical feature which allows us to place the constructor code directly inside the type declaration and to write the constructor arguments as part of the type construct. In our example, the constructor initializes the mutable field and prints a string as a side effect. F# also supports explicit constructors that have similar syntax as other members, but these are needed rarely.
In the previous example we implemented a concrete object type (a class), which means that it is possible to create an instance of the type and call its methods in the code. In the next example we will look at declaration of an interface (called abstract object type in F#). As you can see, it is similar to the declaration of a class:
type AnyCell =
abstract Value : int with get, set
abstract Print : unit -> unit
The interesting concept in the F# object oriented support is that it is not needed to explicitly specify whether the object type is abstract (interface), concrete (class) or partially implemented (class with abstract methods), because the F# complier infers this automatically depending on the members of the type. Abstract object types (interfaces) can be implemented by a concrete object type (class) or by an object expression, which will be discussed shortly. When implementing an interface in an object type we use the interface .. with construct and define the members required by the interface. Note that the indentation is significant in the lightweight F# syntax, meaning that the members implementing the interface type have to be indented further:
type ImplementCell(n:int) =
let mutable data = n + 1
interface AnyCell with
member x.Print() = printf "Data: %d" data
member x.Value
with get() = data
and set(v) = data <- v
The type casts supported by F# are upcast, used for casting an object to a base type or to an implemented interface type (written as o :> TargetType), downcast, used for casting back from a base type (written as o :?> TargetType), which throws an exception when the value isn’t a value of the specified type and finally, a dynamic type test (written as o :? TargetType), which tests whether a value can be casted to a specified type.
Object expressions
As already mentioned, abstract types can be also implemented by an object expression. This allows us to implement an abstract type without creating a concrete type and it is particularly useful when you need to return an implementation of a certain interface from afunction or build an implementation on the fly using functions already defined somewhere else in your program. The following example implements the AnyCell type:
> let newCell n =
let data = ref n
{ new AnyCell with
member x.Print() = printf "Data: %d" (!data)
member x.Value
with get() = !data
and set(v) = data:=v };;
val newCell : int -> AnyCell
In this code we created a function that takes an initial value as an argument and returns a cell holding this value. In this example we use one more type of mutable values available in F#, called reference cell, which are similar to a mutable values, but more flexible (the F# compiler doesn’t allow using an ordinary mutable value in this example). A mutable cell is created by a ref function taking an initial value. The value is accessed using a prefix ! operator and can be modified using := operator. When implementing the abstract type, we use a new ... with construct with members implementing the functionality required by the abstract type (an object expression can’t add any members). In this example we need a reference cell to hold the value, so the cell is declared in a function and captured in a closure, which means that it will exist until the returned object will be garbage collected.