jonnifyfsharp

Dive C2.06.2

A real interface, no class

When the type is named and a function will not do, write the instance where it is used.

A bare function serves the one-method case, but not every interface collapses to a function. When the contract is named and a value of that named type is required — IDisposable for a use block, an IComparer<int> a BCL collection requires, a contract with several members — F# needs an instance. C# supplies one by declaring a class, often a private sealed throwaway used in a single place. F# supplies it with an object expression: { new T with member … } builds an instance of T inline, no class declared, right where it is read.

C2.06.2 · part 1

{ new T with member … }

An object expression names an interface (or a base type) and supplies its members in one expression. The result is a value of that interface type, ready to hand to anything that requires one. The simplest case is a one-method interface that the rest of a system still expects as a type rather than a function — IDisposable is the everyday example, because a use binding and many BCL calls require it by name.

let disposable =
    { new System.IDisposable with
        member _.Dispose() = printfn "disposed" }
// val disposable : System.IDisposable

The type of disposable is System.IDisposable — not a class name, because no class was declared. The object expression produced an anonymous implementation that satisfies the interface. Where a C# program would write a small named class to do this once, { new … with … } does it where the value is needed.

{ new T with … }
the object-expression form: name the interface T, supply its members, get a value of type T.
member _.M(…)
a member of the implemented interface; the _ stands for the self-identifier, unused here.
val : IDisposable
the inferred type is the interface itself — there is no class name because none was written.
vs. a class
C# needs a private sealed class to implement a one-off interface; the object expression skips the declaration.
C2.06.2 · part 2

The C# the reader knows, beside the F#

Take a comparer a BCL API requires as an IComparer<int> — reverse order. C# writes a class implementing the interface. F# writes the object expression: the same Compare member, but no class declaration around it. The compare b a body reverses the usual order, so Compare(1, 2) returns a positive number where the natural comparer would return a negative one.

C# — a named class to implement one interface

sealed class Reverse : IComparer<int> {
    public int Compare(int a, int b) => b.CompareTo(a);   // reverse
}
var cmp = new Reverse();
cmp.Compare(1, 2);   // 1  (because 2 sorts before 1)

F# — an object expression, no class declared

let cmp =
    { new System.Collections.Generic.IComparer<int> with
        member _.Compare(a, b) = compare b a }      // reverse order
// val cmp : System.Collections.Generic.IComparer<int>
cmp.Compare(1, 2)                                  // val it : int = 1

The two are the same interface and the same behaviour. The difference is the ceremony: C# names a type that exists only to hold one method; F# writes that method directly in an instance. Both values are an IComparer<int>, and either can be handed to a sort or any BCL API that asks for one.

Sort with the inline comparer

One fixed array, [| 3; 1; 4; 1; 5; 9; 2; 6 |]. Pick which IComparer<int> the sort uses: the natural one (compare a b) or the reverse object expression (compare b a). The readout shows what Compare(1, 2) returns for the chosen comparer — its sign sets the order — and the array sorted with it.

an IComparer<int> sets the order

the comparer member _.Compare(a, b) = compare a b Compare(1, 2) = -1 → ascending Array.sortWith · result
natural · Compare(1, 2) = -1 · Array.sortWith gives [|1; 1; 2; 3; 4; 5; 6; 9|]
Without JavaScript: the two comparers, by hand
comparerCompare(1, 2)sorted array
compare a b-1[|1; 1; 2; 3; 4; 5; 6; 9|]
compare b a1[|9; 6; 5; 4; 3; 2; 1; 1|]

The comparer is a value of a named interface type, built where it is used. No class file holds one method; the Compare body sits in plain view, and the sort reads its sign.

C2.06.2 · part 3

When to reach for it

The rule is narrow, which is the point. Reach for a function first: if the API takes a comparison or a callback and is happy with a function value, pass the function. Reach for an object expression only when a named interface is required as a type — a use binding that requires IDisposable, a BCL method whose parameter is IComparer<T>, a multi-method contract. The object expression gives the named instance without the named class.

An object expression can implement several members at once and can extend a base class as well as an interface, so the multi-method contract is covered by the same form — list each member inside the braces.

C# habit

A private sealed class implementing a named interface used in one place — declared, instantiated, discarded.

F# form

{ new IComparer<int> with member _.Compare(a, b) = compare b a } — the instance built inline, no class.

Where this leads

Functions cover the one-method interface; object expressions cover the named one. The remaining piece is the seam with .NET's own carriers of behaviour — the Func and Action delegates a C# API hands you and expects back. The next dive shows an F# lambda and a Func as the same value crossing in either direction.

References

Sources

Related in this course