TP

F# Overview (III.) - Imperative and Object-Oriented Programming

In the third part of the F# Overview article series, we will look at language features that are mostly well known, because they are present in most of the currently used programming languages. Indeed, I'm talking about imperative programming, which is a common way for storing and manipulating application data and about object oriented programming which is used for structuring complex programs.

In general, F# tries to make using them together with the functional constructs described in the previous part [^] as natural as possible, which yields several very powerful constructs.

Imperative Programming and Mutable Values

Similarly as ML and OCaml, F# adopts an eager evaluation mechanism, which means that a code written using sequencing operator is executed in the same order in which it is written and expressions given as an arguments to a function are evaluated before calling the function (this mechanism is used in most imperative languages including C#, Java or Python). This makes it semantically reasonable to support imperative programming features in a functional language. As already mentioned, the F# value bindings are by default immutable, so to make a variable mutable the mutable keyword has to be used. Additionally F# supports a few imperative language constructs (like for and while), which are expressions of type unit:

> // Imperative factorial calculation
  let n = 10
  let mutable res = 1
  for n = 2 to n do
    res <- res * n    
  // Return the result
  res;;
val it : int = 3628800

The use of the eager evaluation and the ability to use mutable values makes it very easy to interoperate with other .NET languages (that rely on the use of mutable state), which is an important aspect of the F# language. In addition it is also possible to use the mutable keyword for creating a record type with a mutable field.

Arrays

As mentioned earlier, another type of value that can be mutated is .NET array. Arrays can be either created using [| .. |] expressions (in the following example we use it together with a range expression, which initializes an array with a range) or using functions from the Array module, for example Array.create. Similarly to the mutable variables introduced in the previous section, the value of an array element can be modified using the <- operator:

> let arr = [| 1 .. 10 |]
val arr : array<int>

> for i = 0 to 9 do 
    arr.[i] <- 11 - arr.[i]
(...)

> arr;;
val it : array<int> = [| 10; 9; 8; 7; 6; 5; 4; 3; 2; 1 |]

.NET interoperability

The .NET BCL is built in an object oriented way, so the ability to work with existing classes is essential for the interoperability. Many (in fact almost all) of the classes are also mutable, so the eager evaluation and the support for side-effects are two key features when working with any .NET library. The following example demonstrates working with the mutable generic ResizeArray<T> type from the BCL (ResizeArray is an alias for a type System.Collections.Generic.List to avoid a confusion with the F# list type):

> let list = new ResizeArray<_>()
  list.Add("hello")
  list.Add("world")
  Seq.to_list list;;
val it : string list = ["hello"; "world"]

As you can see, we used the underscore symbol when creating an instance of the generic type, because the type inference algorithm in F# can deduce the type argument from the code (in this example it infers that the type argument is string, because the Add method is called with a string as an argument). After creating an instance we used Add method to modify the list and add two new items. Finally, we used a Seq.to_list function to convert the collection to the F# list.

As a fully compatible .NET language, F# also provides a way for declaring its own classes (called object types in F#), which are compiled into CLR classes or interfaces and therefore the types can be accessed from any other .NET language as well as used to extend classes written in other .NET languages. This is an important feature that allows accessing complex .NET libraries like Windows Forms or ASP.NET from F#.

Object Oriented Programming

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)

The object 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 a function 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.

Adding Members to F# Types

Probably the most important advantage of using object types is that they hide an implementation of the type, which makes it possible to modify the implementation without breaking the existing code that uses them. On the other side, basic F# types like discriminated unions or records expose the implementation details, which can be problematic in some cases, especially when developing a larger application. Also, the dot-notation used with object types makes it very easy to discover operations supported by the type. To bridge this problem, F# allows adding members to both discriminated unions and record types:

> type Variant =
    | Num of int
    | Str of string with
    member x.Print() =
      match x with
      | Num(n) -> printf "Num %d" n
      | Str(s) -> printf "Str %s" s;;
(...)

> let v = Num 42
  v.Print();;
Num 42

In this example we declared a type called Variant which can contain either a number or a string value and added a member Print that can be invoked using dot-notation. Aside from adding members (both methods an properties) it is also possible to implement an abstract object type by a record or discriminated union using the interface ... with syntax mentioned earlier.

Rather than writing all code using member syntax, it is often more elegant to implement the functionality associated with an F# type in a function and then use type augmentations to make this functionality available as a member via dot-notation. This is a pattern used very often in the F# library implementation and I personally believe that it makes the code more readable. The following example re-implements the Variant type using this pattern:

type Variant =
  | Num of int
  | Str of string
  
let print x =   
  match x with
  | Num(n) -> printf "Num %d" n
  | Str(s) -> printf "Str %s" s

type Variant with
  member x.Print() = print x

The construct type ... with is a type augmentation, which adds the member Print to a type declared earlier in the code. The type augmentation has to be included in a same compilation unit as the declared type - usually in a same file. It is also possible to attach extension members to a type declared in a different compilation unit - the main difference is that these members are just a syntactical sugar and are not a part of the original type, meaning that they can't access any implementation details of the type. The only reason for using extension members is that they make your function for working with the type available using the dot-notation, which can simplify the code a lot and it will be easier to find the function (for example it will be available in the Visual Studio IntelliSense). When declaring an extension member you use the same syntax as for type augmentations with the difference that the name of the type has to be fully qualified as demonstrated in the following example:

> type System.Collections.Generic.List<'a> with
    member x.ToList() = Seq.to_list x;
(...)
    
> let r = new ResizeArray<_>()
  r.Add(1)
  r.Add(2)
  r.ToList();;
val it : list<int> = [ 1; 2 ]

In this example we use extension members to add a ToList method to an existing .NET generic type. Note that when declaring the extension members we have to use the original type name and not the F# alias. You should also bear in mind that extension members are resolved by the F# compiler and so calling them from C# will not be easily possible. In general, extension members are not declared very often, but some parts of the F# library (for example the features for asynchronous and parallel programming) use them.

Article Series Links

Published: Saturday, 3 November 2007, 12:00 AM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: functional, f#