Interfaces, Inheritance, and Object Expressions
Prerequisites: Classes and members
Interfaces
An interface is a set of members a type promises to provide:
type IShape =
abstract Area: float
abstract Name: string
type Circle(r: float) =
interface IShape with
member _.Area = System.Math.PI * r * r
member _.Name = "circle"
type Square(side: float) =
interface IShape with
member _.Area = side * side
member _.Name = "square"
let shapes: IShape list = [ Circle(1.0); Square(2.0) ]
for s in shapes do
printfn "%s: %.2f" s.Name s.Area
circle: 3.14
square: 4.00
F# implements interfaces explicitly: to call Area on a Circle,
you need an IShape value. Putting them in an IShape list converts
them automatically; elsewhere, upcast with :>
((Circle 1.0 :> IShape).Area).
Inheritance
[<AbstractClass>]
type Animal(name: string) =
member _.Name = name
abstract Sound: string
default _.Sound = "..."
member this.Speak() = $"{this.Name} says {this.Sound}"
type Dog(name) =
inherit Animal(name)
override _.Sound = "Woof"
type Fish(name) =
inherit Animal(name)
let animals: Animal list = [ Dog "Rex"; Fish "Nemo" ]
for a in animals do
printfn "%s" (a.Speak())
match animals[0] with
| :? Dog -> printfn "the first animal is a dog"
| _ -> printfn "not a dog"
Rex says Woof
Nemo says ...
the first animal is a dog
abstractdeclares a member;defaultgives it a default implementation;overridereplaces it in a subclass.:?tests the runtime type, and:?>downcasts (it throws if the cast is wrong, so prefer the pattern).
In idiomatic F#, deep inheritance hierarchies are rare. A discriminated union is usually better for a closed set of variants (all shapes you know about), and interfaces for an open set (shapes other people can add).
Object expressions
An object expression creates an object implementing an interface on the spot, without declaring a class:
type ILogger =
abstract Log: string -> unit
let consoleLogger prefix =
{ new ILogger with
member _.Log message = printfn "[%s] %s" prefix message }
let run (logger: ILogger) items =
for i in items do
logger.Log $"processing {i}"
run (consoleLogger "app") [1; 2]
let byLength =
{ new System.Collections.Generic.IComparer<string> with
member _.Compare(a, b) = compare a.Length b.Length }
let words = System.Collections.Generic.List<string>([ "ccc"; "a"; "bb" ])
words.Sort byLength
printfn "%A" (List.ofSeq words)
[app] processing 1
[app] processing 2
["a"; "bb"; "ccc"]
Object expressions are great for small adapters, test doubles, and
.NET interfaces like IComparer and IDisposable.
Interfaces on records and unions
Records and unions can implement interfaces too:
type IDescribe =
abstract Describe: unit -> string
type Product =
{ Name: string; Price: decimal }
interface IDescribe with
member this.Describe() = $"{this.Name} costs {this.Price}"
let p = { Name = "Tea"; Price = 3.5m }
printfn "%s" ((p :> IDescribe).Describe())
Tea costs 3.5
Practice
Define an interface IDiscount with a method Apply: decimal -> decimal.
Create two discounts with object expressions — 10% off, and 5 off with a
minimum price of 0 — and apply both to 30 and to 3.
Show solution
type IDiscount =
abstract Apply: decimal -> decimal
let percentOff (p: decimal) =
{ new IDiscount with
member _.Apply price = price * (1m - p / 100m) }
let amountOff (a: decimal) =
{ new IDiscount with
member _.Apply price = max 0m (price - a) }
for d in [ percentOff 10m; amountOff 5m ] do
printfn "%M %M" (d.Apply 30m) (d.Apply 3m)
27.0 2.7
25 0