F# Training
F# Training
F# Training
  • Presentation
  • Fundamentals
    • Introduction
    • Syntax
      • Bases
      • Functions
      • Rules
      • Exceptions
    • First concepts
    • πŸ”Quiz
  • Functions
    • Signature
    • Concepts
    • Syntax
    • Standard functions
    • Operators
    • Addendum
    • πŸ”Quiz
    • πŸ“œSummary
  • Types
    • Overview
    • Tuples
    • Records
    • Unions
    • Enums
    • Anonymous records
    • Value types
    • πŸ“œRecap
    • Addendum
  • Monadic types
    • Intro
    • Option type
    • Result type
    • Smart constructor
    • πŸš€Computation expression (CE)
    • πŸš€CE theoretical basements
    • πŸ“œRecap
  • Pattern matching
    • Patterns
    • Match expression
    • Active patterns
    • πŸš€Fold function
    • πŸ“œRecap
    • πŸ•ΉοΈExercises
  • Collections
    • Overview
    • Types
    • Common functions
    • Dedicated functions
    • πŸ”Quiz
    • πŸ“œRecap
  • Asynchronous programming
    • Asynchronous workflow
    • Interop with .NET TPL
    • πŸ“œRecap
  • Module and Namespace
    • Overview
    • Namespace
    • Module
    • πŸ”Quiz
    • πŸ“œRecap
  • Object-oriented
    • Introduction
    • Members
    • Type extensions
    • Class, Struct
    • Interface
    • Object expression
    • Recommendations
Powered by GitBook
On this page
  • Definition
  • Intrinsic extension
  • Optional extension
  • Limits
  • Type extension vs virtual methods
  • Type extension vs type alias
  • Type extension vs Generic type constraints
  • Extension method (Cβ™―-style)
  • Tuples
  • Comparison
  • Limits
  • Extensions vs Cβ™― partial class

Was this helpful?

Edit on GitHub
  1. Object-oriented

Type extensions

Definition

Members of a type defined outside its main type block.

Each of these members is called augmentation or extension.

3 categories of extension :

  • Intrinsic extension

  • Optional extension

  • Extension methods

Intrinsic extension

Declared in the same file and namespace as the type

Use case: Features available in both companion module and type β†’ E.g. List.length list function and list.Length member

How to implement it following top-down declarations?

1. Implement in type, Redirect module functions to type members β†’ More straightforward

2. Intrinsic extensions: β†’ Declare type "naked", Implement in module, Augment type after β†’ Favor FP style, Transparent for Interop

Example:

namespace Example

type Variant =
    | Num of int
    | Str of string

module Variant =
    let print v =
        match v with
        | Num n -> printf "Num %d" n
        | Str s -> printf "Str %s" s

// Add a member as an extension - see `with` required keyword
type Variant with
    member x.Print() = Variant.print x

Optional extension

Extension defined outside the type module/namespace/assembly

Use cases:

  1. Types we can't modify, for instance coming from a library

  2. Keep types naked - e.g. Elmish MVU pattern

module EnumerableExtensions

open System.Collections.Generic

type IEnumerable<'T> with
    /// Repeat each element of the sequence n times
    member xs.RepeatElements(n: int) =
        seq {
            for x in xs do
                for _ in 1 .. n -> x
        }

// Usage: in another file ---
open EnumerableExtensions

let x = [1..3].RepeatElements(2) |> List.ofSeq
// [1; 1; 2; 2; 3; 3]

Compilation: into static methods β†’ Simplified version of the previous example:

public static class EnumerableExtensions
{
    public static IEnumerable<T> RepeatElements<T>(IEnumerable<T> xs, int n) {...}
}

Another example:

// Person.fs ---
type Person = { First: string; Last: string }

// PersonExtensions.fs ---
module PersonExtensions =
    type Person with
        member this.FullName =
            $"{this.Last.ToUpper()} {this.First}"

// Usage elsewhere ---
open PersonExtensions
let joe = { First = "Joe"; Last = "Dalton" }
let s = joe.FullName  // "DALTON Joe"

Limits

  • Must be declared in a module

  • Not compiled into the type, not visible to Reflection

  • Usage as pseudo-instance members only in Fβ™― β‰  in Cβ™―: as static methods

Type extension vs virtual methods

☝ Override virtual methods:

  • in the initial type declaration βœ…

  • not in a type extension β›”

type Variant = Num of int | Str of string with
    override this.ToString() = ... βœ…

module Variant = ...

type Variant with
    override this.ToString() = ... ⚠️
    // Warning FS0060: Override implementations in augmentations are now deprecated...

Type extension vs type alias

Incompatible❗

type i32 = System.Int32

type i32 with
    member this.IsEven = this % 2 = 0
// πŸ’₯ Error FS0964: Type abbreviations cannot have augmentations

πŸ’‘ Solution: use the real type name

type System.Int32 with
    member this.IsEven = this % 2 = 0

☝ Corollary: Fβ™― tuples such as int * int cannot be augmented, but they can be extended using Cβ™―-style extension methods πŸ“

Type extension vs Generic type constraints

Extension allowed on generic type except when constraints differ:

open System.Collections.Generic

type IEnumerable<'T> with
//   ~~~~~~~~~~~ πŸ’₯ Error FS0957
//   One or more of the declared type parameters for this type extension
//   have a missing or wrong type constraint not matching the original type constraints on 'IEnumerable<_>'
    member this.Sum() = Seq.sum this

// ☝ This constraint comes from `Seq.sum`.

Solution: Cβ™―-style extension method πŸ“

Extension method (Cβ™―-style)

Static method:

  • Decorated with [<Extension>]

  • In Fβ™― < 8.0: Defined in class decorated with [<Extension>]

  • Type of 1st argument = extended type (IEnumerable<'T> below)

Example:

open System.Runtime.CompilerServices

[<Extension>] // πŸ’‘ Not required anymore since Fβ™― 8.0
type EnumerableExtensions =
    [<Extension>]
    static member inline Sum(xs: seq<_>) = Seq.sum xs

let x = [1..3].Sum()
//------------------------------
// Output in FSI console (verbose syntax):
type EnumerableExtensions =
  class
    static member
      Sum : xs:seq<^a> -> ^a
              when ^a : (static member ( + ) : ^a * ^a -> ^a)
              and  ^a : (static member get_Zero : -> ^a)
  end
val x : int = 6

Cβ™― equivalent:

using System.Collections.Generic;

namespace Extensions
{
    public static class EnumerableExtensions
    {
        public static TSum Sum<TItem, TSum>(this IEnumerable<TItem> source) {...}
    }
}

Tuples

An extension method can be added to any Fβ™― tuple:

open System.Runtime.CompilerServices

[<Extension>]
type EnumerableExtensions =
    [<Extension>]
    // Signature : ('a * 'a) -> bool when 'a : equality
    static member inline IsDuplicate((x, y)) = // πŸ‘ˆ Double () required
        x = y

let b1 = (1, 1).IsDuplicate()  // true
let b2 = ("a", "b").IsDuplicate()  // false

Comparison

Feature
Type extension
Extension method

Methods

βœ… instance, βœ… static

βœ… instance, ❌ static

Properties

βœ… instance, βœ… static

❌ Not supported

Constructors

βœ… intrinsic, ❌ optional

❌ Not supported

Extend constraints

❌ Not supported

βœ… Support SRTP

Limits

Type extensions do not support (sub-typing) polymorphism:

  • Not in the virtual table

  • No virtual, abstract member

  • No override member (but overloads πŸ‘Œ)

Extensions vs Cβ™― partial class

Feature
Multi-files
Compiled into the type
Any type

Cβ™― partial class

βœ… Yes

βœ… Yes

Only partial class

Extension intrinsic

❌ No

βœ… Yes

βœ… Yes

Extension optional

βœ… Yes

❌ No

βœ… Yes

PreviousMembersNextClass, Struct

Last updated 1 month ago

Was this helpful?

☝ Note: The actual implementations of Sum() in LINQ are different, one per type: int, float... β†’

Source code