Type extension is a feature that allows you to add members to a previously defined object type. It is also known as augmentation.
In F#, the following type extension features are available:
Example:
namespace DasDocs
type Shape =
| Square of int
| Rectangle of int * int
module Shape =
let area shape =
match shape with
| Square side -> side * side
| Rectangle (length, breadth) -> length * breadth
type Shape with
member this.Area() = Shape.area this
Advantages of intrinsic type extension:
Shape
type without having to define any member methodsarea
which takes in Shape
type instance as input. Example: let area = shape |> Shape.area
Shape
so that you can access area
function using object-style .
notation. Example: let area = shape.Area()
For example, let's say you add a library DasDocs
to your project which has Shape
type defined as above. You can extend Shape
as follows:
module Geometry
open DasDocs
type Shape with
member this.Area() = Shape.area this
Another example is extending IEnumerable
interface defined in System.Collections.Generic
module with a custom method:
open System.Collections.Generic
type IEnumerable<'T> with
/// Print all elements
member xs.PrintElements() =
for x in xs do
printfn $"{x}"
let nums1to10 = seq { for i in 1..10 do yield i }
nums1to10.PrintElements()
// prints numbers 1 to 10 in a new line
open
ed. Like we need to open Geometry
to use PrintElements
method above.As seen above in the case of extending IEnumerable
with PrintElements
method, it is possible to extend a generic type. The requirement for extending a generic type is that the constraint of the extension declaration matches the constraint of the declared type.
However, when the constraints differ, the code does not compile:
type IEnumerable<'T> with
member inline this.Sum() = Seq.sum this
This will give the following compilation error:
error FS0670: This code is not sufficiently generic. The type variable ^T when ^T: (static member get_Zero: -> ^T) and ^T: (static member (+) : ^T * ^T -> ^T) could not be generalized because it would escape its scope.
Seq.sum
function requires the following constraints on 'T
: static member get_Zero
and static member (+)
.Sum
method, this would still result in an error as it violates the constraint requirements set by IEnumberable<'T>
member this.Sum
to member inline this.Sum
also does not work as it gives type constraint mismatch error:error FS0339: The signature and implementation are not compatible because the type parameter in the class/signature has a different compile-time requirement to the one in the member/implementation
To overcome this limitation, we need to use extension methods.
Extension methods are useful when you want to define extensions on a generic type that will constraint the type variable:
// required for IEnumberable
open System.Collections.Generic
// required for [<Extension>]
open System.Runtime.CompilerServices
[<Extension>]
type IEnumerableExtensions =
[<Extension>]
static member inline Sum(xs: IEnumerable<'T>) = Seq.sum xs
IEnumerableExtensions
) is not important, so you could essentially name it anything you want.Sum
method belongs to by looking at the first parameter's type, here being IEnumerable<'T>
In F# interactive (FSI), you get the following type signature for Sum
method:
type IEnumerableExtensions =
static member
inline Sum: xs: IEnumerable< ^T> -> ^T
when ^T: (static member (+) : ^T * ^T -> ^T) and
^T: (static member get_Zero: -> ^T)
You can now use this Sum
method as before:
let nums1To10 = seq { for i in 1..10 do yield i }
printfn "%d" (nums1To10.Sum())
// prints 55
© 2022 Sumeet Das