F Sharp Programming/Interfaces

From Wikibooks, open books for an open world
< F Sharp Programming
Jump to: navigation, search
Previous: Inheritance Index Next: Events
F# : Interfaces


An object's interface refers to all of the public members and functions that a function exposes to consumers of the object. For example, take the following:

type Monkey(name : string, birthday : DateTime) =
    let mutable _birthday = birthday
    let mutable _lastEaten = DateTime.Now
    let mutable _foodsEaten = [] : string list
 
    member this.Speak() = printfn "Ook ook!"
    member this.Name = name
    member this.Birthday
        with get() = _birthday
        and set(value) = _birthday <- value
 
    member internal this.UpdateFoodsEaten(food) = _foodsEaten <- food :: _foodsEaten
    member internal this.ResetLastEaten() = _lastEaten <- DateTime.Now
    member this.IsHungry = (DateTime.Now - _lastEaten).TotalSeconds >= 5.0
    member this.GetFoodsEaten() = _lastEaten
    member this.Feed(food) =
        this.UpdateFoodsEaten(food)
        this.ResetLastEaten()
        this.Speak()

This class contains several public, private, and internal members. However, consumers of this class can only access the public members; when a consumer uses this class, they see the following interface:

type Monkey =
  class
    new : name:string * birthday:DateTime -> Monkey
    member Feed : food:string -> unit
    member GetFoodsEaten : unit -> DateTime
    member Speak : unit -> unit
    member Birthday : DateTime
    member IsHungry : bool
    member Name : string
    member Birthday : DateTime with set
  end

Notice the _birthday, _lastEaten, _foodsEaten, UpdateFoodsEaten, and ResetLastEaten members are inaccessible to the outside world, so they are not part of this object's public interface.

All interfaces you've seen so far have been intrinsically tied to a specific object. However, F# and many other OO languages allow users to define interfaces as stand-alone types, allowing us to effectively separate an object's interface from its implementation.

Defining Interfaces[edit]

According to the F# specification, interfaces are defined with the following syntax:

type type-name = 
   interface
       inherits-decl 
       member-defns 
   end
Note: The interface/end tokens can be omitted when using the #light syntax option, in which case Type Kind Inference (§10.1) is used to determine the kind of the type. The presence of any non-abstract members or constructors means a type is not an interface type.

For example:

type ILifeForm = (* .NET convention recommends the prefix 'I' on all interfaces *)
    abstract Name : string
    abstract Speak : unit -> unit
    abstract Eat : unit -> unit


Using Interfaces[edit]

Since they only define a set of public method signatures, users need to create an object to implement the interface. Here are three classes which implement the ILifeForm interface in fsi:

> type ILifeForm =
    abstract Name : string
    abstract Speak : unit -> unit
    abstract Eat : unit -> unit
 
type Dog(name : string, age : int) =
    member this.Age = age
 
    interface ILifeForm with
        member this.Name = name
        member this.Speak() = printfn "Woof!"
        member this.Eat() = printfn "Yum, doggy biscuits!"
 
type Monkey(weight : float) =
    let mutable _weight = weight
 
    member this.Weight
        with get() = _weight
        and set(value) = _weight <- value
 
    interface ILifeForm with
        member this.Name = "Monkey!!!"
        member this.Speak() = printfn "Ook ook"
        member this.Eat() = printfn "Bananas!"
 
type Ninja() = 
    interface ILifeForm with
        member this.Name = "Ninjas have no name"
        member this.Speak() = printfn "Ninjas are silent, deadly killers"
        member this.Eat() =
            printfn "Ninjas don't eat, they wail on guitars because they're totally sweet";;
 
type ILifeForm =
  interface
    abstract member Eat : unit -> unit
    abstract member Speak : unit -> unit
    abstract member Name : string
  end
type Dog =
  class
    interface ILifeForm
    new : name:string * age:int -> Dog
    member Age : int
  end
type Monkey =
  class
    interface ILifeForm
    new : weight:float -> Monkey
    member Weight : float
    member Weight : float with set
  end
type Ninja =
  class
    interface ILifeForm
    new : unit -> Ninja
  end

Typically, we call an interface an abstraction, and any class which implements the interface as a concrete implementation. In the example above, ILifeForm is an abstraction, whereas Dog, Monkey, and Ninja are concrete implementations.

Its worth noting that interfaces only define instance members signatures on objects. In other words, they cannot define static member signatures or constructor signatures.

What are interfaces used for?[edit]

Interfaces are a mystery to newbie programmers (after all, what's the point of creating a type with no implementation?), however they are essential to many object-oriented programming techniques. Interfaces allow programmers to generalize functions to all classes which implement a particular interface, even if those classes don't necessarily descend from one another. For example, using the Dog, Monkey, and Ninja classes defined above, we can write a method to operate on all of them, as well as any other classes which implement the ILifeForm interface.

Implementing Interfaces with Object Expressions[edit]

Interfaces are extremely useful for sharing snippets of implementation logic between other classes, however it can be very cumbersome to define and implement a new class for ad hoc interfaces. Object expressions allow users to implement interfaces on anonymous classes using the following syntax:

{ new ty0 [ args-expr ] [ as base-ident ] [ with 
      val-or-member-defns end ]
 
  interface ty1 with [ 
      val-or-member-defns1 
   end ]
 
  …
 
  interface tyn with [ 
      val-or-member-defnsn  
  end ] }

Using a concrete example, the .NET BCL has a method called System.Array.Sort<T>(T array, IComparer<T>), where IComparer<T> exposes a single method called Compare. Let's say we wanted to sort an array on an ad hoc basis using this method; rather than litter our code with one-time use classes, we can use object expressions to define anonymous classes on the fly:

> open System
open System.Collections.Generic
 
type person = { name : string; age : int }
 
let people =
    [| { name = "Larry"; age = 20 };
      { name = "Moe"; age = 30 };
      { name = "Curly"; age = 25 } |]
 
let sortAndPrint msg items (comparer : System.Collections.Generic.IComparer<person>) =
    Array.Sort(items, comparer)
    printf "%s: " msg
    Seq.iter (fun x -> printf "(%s, %i) " x.name x.age) items
    printfn ""
 
(* sorting by age *)    
sortAndPrint "age" people { new IComparer<person> with member this.Compare(x, y) = x.age.CompareTo(y.age) }
 
(* sorting by name *)
sortAndPrint "name" people { new IComparer<person> with member this.Compare(x, y) = x.name.CompareTo(y.name) }
 
(* sorting by name descending *)
sortAndPrint "name desc" people { new IComparer<person> with member this.Compare(x, y) = y.name.CompareTo(x.name) };;
 
type person =
  {name: string;
   age: int;}
val people : person array
val sortAndPrint : string -> person array -> IComparer<person> -> unit
 
age: (Larry, 20) (Curly, 25) (Moe, 30) 
name: (Curly, 25) (Larry, 20) (Moe, 30) 
name desc: (Moe, 30) (Larry, 20) (Curly, 25)

Implementing Multiple Interfaces[edit]

Unlike inheritance, its possible to implement multiple interfaces:

open System
 
type Person(name : string, age : int) = 
    member this.Name = name
    member this.Age = age
 
    (* IComparable is used for ordering instances *)
    interface IComparable<Person> with
        member this.CompareTo(other) =
            (* sorts by name, then age *)
            match this.Name.CompareTo(other.Name) with
            | 0 -> this.Age.CompareTo(other.Age)
            | n -> n
 
    (* Used for comparing this type against other types *)
    interface IEquatable<string> with
        member this.Equals(othername) = this.Name.Equals(othername)

Its just as easy to implement multiple interfaces in object expressions as well.

Interface Hierarchies[edit]

Interfaces can extend other interfaces in a kind of interface hierarchy. For example:

type ILifeForm =
    abstract member location : System.Drawing.Point
 
type 'a IAnimal =   (* interface with generic type parameter *)
    inherit ILifeForm
    inherit System.IComparable<'a>
    abstract member speak : unit -> unit
 
type IFeline =
    inherit IAnimal<IFeline>
    abstract member purr : unit -> unit

When users create a concrete implementation of IFeline, they are required to provide implementations for all of the methods defined in the IAnimal, IComparable, and ILifeForm interfaces.

Note: Interface hierarchies are occasionally useful, however deep, complicated hierarchies can be cumbersome to work with.

Examples[edit]

Generalizing a function to many classes[edit]

open System
type ILifeForm =
    abstract Name : string
    abstract Speak : unit -> unit
    abstract Eat : unit -> unit
 
type Dog(name : string, age : int) =
    member this.Age = age
 
    interface ILifeForm with
        member this.Name = name
        member this.Speak() = printfn "Woof!"
        member this.Eat() = printfn "Yum, doggy biscuits!"
 
type Monkey(weight : float) =
    let mutable _weight = weight
 
    member this.Weight
        with get() = _weight
        and set(value) = _weight <- value
 
    interface ILifeForm with
        member this.Name = "Monkey!!!"
        member this.Speak() = printfn "Ook ook"
        member this.Eat() = printfn "Bananas!"
 
type Ninja() = 
    interface ILifeForm with
        member this.Name = "Ninjas have no name"
        member this.Speak() = printfn "Ninjas are silent, deadly killers"
        member this.Eat() =
            printfn "Ninjas don't eat, they wail on guitars because they're totally sweet"
 
let lifeforms =
    [(new Dog("Fido", 7) :> ILifeForm);
     (new Monkey(500.0) :> ILifeForm);
     (new Ninja() :> ILifeForm)]            
 
let handleLifeForm (x : ILifeForm) =
    printfn "Handling lifeform '%s'" x.Name
    x.Speak()
    x.Eat()
    printfn ""
 
let main() =
    printfn "Processing...\n"
    lifeforms |> Seq.iter handleLifeForm
    printfn "Done."
 
main()

This program has the following types:

type ILifeForm =
  interface
    abstract member Eat : unit -> unit
    abstract member Speak : unit -> unit
    abstract member Name : string
  end
 
type Dog =
  class
    interface ILifeForm
    new : name:string * age:int -> Dog
    member Age : int
  end
 
type Monkey =
  class
    interface ILifeForm
    new : weight:float -> Monkey
    member Weight : float
    member Weight : float with set
  end
 
type Ninja =
  class
    interface ILifeForm
    new : unit -> Ninja
  end
 
val lifeforms : ILifeForm list
val handleLifeForm : ILifeForm -> unit
val main : unit -> unit

This program outputs the following:

Processing...

Handling lifeform 'Fido'
Woof!
Yum, doggy biscuits!

Handling lifeform 'Monkey!!!'
Ook ook
Bananas!

Handling lifeform 'Ninjas have no name'
Ninjas are silent, deadly killers
Ninjas don't eat, they wail on guitars because they're totally sweet

Done.

Using interfaces in generic type definitions[edit]

We can constrain generic types in class and function definitions to particular interfaces. For example, let's say that we wanted to create a binary tree which satisfies the following property: each node in a binary tree has two children, left and right, where all of the child nodes in left are less than all of its parent nodes, and all of the child nodes in right are greater than all of its parent nodes.

We can implement a binary tree with these properties defining a binary tree which constrains our tree to the IComparable<T> interface.

Note: .NET has a number of interfaces defined in the BCL, including the very important IComparable<T> interface. IComparable exposes a single method, objectInstance.CompareTo(otherInstance), which should return 1, -1, or 0 when the objectInstance is greater than, less than, or equal to otherInstance respectively. Many classes in the .NET framework already implement IComparable, including all of the numeric data types, strings, and datetimes.

For example, using fsi:

> open System
 
type tree<'a> when 'a :> IComparable<'a> =
    | Nil
    | Node of 'a * 'a tree * 'a tree
 
let rec insert (x : #IComparable<'a>) = function
    | Nil -> Node(x, Nil, Nil)
    | Node(y, l, r) as node ->
        if x.CompareTo(y) = 0 then node
        elif x.CompareTo(y) = -1 then Node(y, insert x l, r)
        else Node(y, l, insert x r)
 
let rec contains (x : #IComparable<'a>) = function
    | Nil -> false
    | Node(y, l, r) as node ->
        if x.CompareTo(y) = 0 then true
        elif x.CompareTo(y) = -1 then contains x l
        else contains x r;;
 
type tree<'a> when 'a :> IComparable<'a>> =
  | Nil
  | Node of 'a * tree<'a> * tree<'a>
val insert : 'a -> tree<'a> -> tree<'a> when 'a :> IComparable<'a>
val contains : #IComparable<'a> -> tree<'a> -> bool when 'a :> IComparable<'a>
 
> let x =
    let rnd = new Random()
    [ for a in 1 .. 10 -> rnd.Next(1, 100) ]
    |> Seq.fold (fun acc x -> insert x acc) Nil;;
 
val x : tree<int>
 
> x;;
val it : tree<int>
= Node
    (25,Node (20,Node (6,Nil,Nil),Nil),
     Node
       (90,
        Node
          (86,Node (65,Node (50,Node (39,Node (32,Nil,Nil),Nil),Nil),Nil),Nil),
        Nil))
 
> contains 39 x;;
val it : bool = true
 
> contains 55 x;;
val it : bool = false

Simple dependency injection[edit]

Dependency injection refers to the process of supplying an external dependency to a software component. For example, let's say we had a class which, in the event of an error, sends an email to the network administrator, we might write some code like this:

type Processor() =
    (* ... *)
    member this.Process items =
        try
            (* do stuff with items *)
        with
            | err -> (new Emailer()).SendMsg("admin@company.com", "Error! " + err.Message)

The Process method creates an instance of Emailer, so we can say that the Processor class depends on the Emailer class.

Let's say we're testing our Processor class, and we don't want to be sending emails to the network admin all the time. Rather than comment out the lines of code we don't want to run while we test, its much easier to substitute the Emailer dependency with a dummy class instead. We can achieve that by passing in our dependency through the constructor:

type IFailureNotifier =
    abstract Notify : string -> unit
 
type Processor(notifier : IFailureNotifier) =
    (* ... *)
    member this.Process items =
        try
            // do stuff with items
        with
            | err -> notifier.Notify(err.Message)
 
(* concrete implementations of IFailureNotifier *)
 
type EmailNotifier() =
    interface IFailureNotifier with
        member Notify(msg) = (new Emailer()).SendMsg("admin@company.com", "Error! " + msg)
 
type DummyNotifier() =
    interface IFailureNotifier with
        member Notify(msg) = () // swallow message
 
type LogfileNotifier(filename : string) =
    interface IFailureNotifer with  
        member Notify(msg) = System.IO.File.AppendAllText(filename, msg)

Now, we create a processor and pass in the kind of FailureNotifier we're interested in. In test environments, we'd use new Processor(new DummyNotifier()); in production, we'd use new Processor(new EmailNotifier()) or new Processor(new LogfileNotifier(@"C:\log.txt")).

To demonstrate dependency injection using a somewhat contrived example, the following code in fsi shows how to hot swap one interface implementation with another:

> #time;;
 
--> Timing now on
 
> type IAddStrategy =
    abstract add : int -> int -> int
 
type DefaultAdder() =
    interface IAddStrategy with
        member this.add x y = x + y
 
type SlowAdder() = 
    interface IAddStrategy with
        member this.add x y =
            let rec loop acc = function
                | 0 -> acc
                | n -> loop (acc + 1) (n - 1)
            loop x y
 
type OffByOneAdder() =
    interface IAddStrategy with
        member this.add x y = x + y - 1
 
type SwappableAdder(adder : IAddStrategy) =
    let mutable _adder = adder
    member this.Adder
        with get() = _adder
        and set(value) = _adder <- value
 
    member this.Add x y = this.Adder.add x y;;
 
type IAddStrategy =
  interface
    abstract member add : int -> (int -> int)
  end
type DefaultAdder =
  class
    interface IAddStrategy
    new : unit -> DefaultAdder
  end
type SlowAdder =
  class
    interface IAddStrategy
    new : unit -> SlowAdder
  end
type OffByOneAdder =
  class
    interface IAddStrategy
    new : unit -> OffByOneAdder
  end
type SwappableAdder =
  class
    new : adder:IAddStrategy -> SwappableAdder
    member Add : x:int -> (int -> int)
    member Adder : IAddStrategy
    member Adder : IAddStrategy with set
  end
 
Real: 00:00:00.000, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
 
> let myAdder = new SwappableAdder(new DefaultAdder());;
 
val myAdder : SwappableAdder
 
Real: 00:00:00.000, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
 
> myAdder.Add 10 1000000000;;
Real: 00:00:00.001, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
val it : int = 1000000010
 
> myAdder.Adder <- new SlowAdder();;
Real: 00:00:00.000, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it : unit = ()
 
> myAdder.Add 10 1000000000;;
Real: 00:00:01.085, CPU: 00:00:01.078, GC gen0: 0, gen1: 0, gen2: 0
val it : int = 1000000010
 
> myAdder.Adder <- new OffByOneAdder();;
Real: 00:00:00.000, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it : unit = ()
 
> myAdder.Add 10 1000000000;;
Real: 00:00:00.000, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it : int = 1000000009
Previous: Inheritance Index Next: Events