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
  • Class
  • Class constructors
  • Primary constructor
  • Secondary constructor
  • Constructor parameters
  • let bindings
  • do bindings
  • Static constructor
  • Singleton
  • Empty class
  • Generic class
  • Instanciation
  • Property initialization
  • Abstract class
  • Fields
  • Implicit fields
  • Explicit fields
  • Field vs property
  • Explicit field vs implicit field vs property
  • Structures

Was this helpful?

Edit on GitHub
  1. Object-oriented

Class, Struct

Class

Class in F♯ ≡ class in C♯

  • Object-oriented building block

  • Constructor of objects containing data of defined type and methods

  • Can be static too

Keyword: we use the type keyword to define a class, like any other type in F♯.

type MyClass =
    // (static) let and do bindings...
    // Constructors...
    // Members...

Class constructors

Primary constructor

Non-static classes are generally followed by their primary constructor:

type CustomerName(firstName: string, lastName: string) =
    // Primary builder's body
    // Members...

☝ Note: The primary constructor parameters, here firstName and lastName, are visible throughout the class body.

Secondary constructor

Syntax for defining another constructor: new(argument-list) = constructor-body

☝ In constructor-body, there must be a call the primary constructor.

type Point(x: float, y: float) =
    new() = Point(0, 0)
    // Members...

Constructor parameters

☝ Constructors accept only tuples. They can not be curried!

let bindings

We can use let bindings in the class definition to define variables (in fact, class fields, called implicit fields) and inner functions.

Syntax:

// Field.
[static] let [ mutable ] binding1 [ and ... binding-n ]

// Function.
[static] let [ rec ] binding1 [ and ... binding-n ]

☝ Notes

  • Declared before the class members

  • Initial value mandatory

  • Private

  • Direct access: no need to qualify them with the self-identifier

Example:

type Person(firstName: string, lastName: string) =
    let fullName = $"{firstName} {lastName}"
    member _.Hi() = printfn $"Hi, I'm {fullName}!"

let p = Person("John", "Doe")
p.Hi()  // Hi, I'm John Doe!

do bindings

We can use do bindings in the class definition to trigger side-effects (like logging) during the construction of the object or of the type, before the members.

Syntax:

[static] do expression

Static constructor

  • There are no explicit static constructors in F♯.

  • We can use static let and static do bindings to perform "type initialization".

  • They will be executed the first time the type is used.

Example:

type K() =
    static let mutable count = 0

    // do binding exécuté à chaque construction
    do
        count <- count + 1

    member val CreatedCount = count

let k1 = K()
let count1 = k1.CreatedCount  // 1
let k2 = K()
let count2 = k2.CreatedCount  // 2

Singleton

You can define a Singleton class by making the primary constructor private:

type S private() =
    static member val Instance = S()

let s = S.Instance  // val s : S

Warning

It's not thread-safe!

💡 Alternative: single-case union perfectly fits this use case with no overhead.

type S = S
let s = S

Empty class

To define a class without a body, use the verbose syntax class ... end:

type K = class end
let k = K()

Generic class

There is no automatic generalization on type → Generic parameters need to be specified:

type Tuple2_KO(item1, item2) = // ⚠️ 'item1' and 'item2': 'obj' type !
    // ...

type Tuple2<'T1, 'T2>(item1: 'T1, item2: 'T2) =  // 👌
    // ...

Instanciation

Call one of the constructors, with tuple arguments

☝️ Don't forget () if no arguments, otherwise you get a function!

In a let binding: new is optional and not recommended → let v = Vector(1.0, 2.0) 👌 → let v = new Vector(1.0, 2.0) ❌

In a use binding: new mandatory → use d = new Disposable()

Property initialization

Properties can be initialized with setters at instantiation 👍

  • Specify them as named arguments in the call to the constructor

  • Place them after any constructor arguments

type PersonName(first: string) =
    member val First = first with get, set
    member val Last = "" with get, set

let p1 = PersonName("John")
let p2 = PersonName("John", Last = "Paul")
let p3 = PersonName(first = "John", Last = "Paul")

💡 Equivalent in C♯: new PersonName("John") { Last = "Paul" }

Abstract class

Annotated with [<AbstractClass>]

One of the members is abstract:

  1. Declared with the abstract keyword

  2. No default implementation (with default keyword) (Otherwise the member is virtual)

Inheritance with inherit keyword → Followed by a call to the base class constructor

Example:

[<AbstractClass>]
type Shape2D() =
    member val Center = (0.0, 0.0) with get, set
    member this.Move(?deltaX: float, ?deltaY: float) =
        let x, y = this.Center
        this.Center <- (x + defaultArg deltaX 0.0,
                        y + defaultArg deltaY 0.0)
    abstract GetArea : unit -> float
    abstract Perimeter : float  with get

type Square(side) =
    inherit Shape2D()
    member val Side = side
    override _.GetArea () = side * side
    override _.Perimeter = 4.0 * side

let o = Square(side = 2.0, Center = (1.0, 1.0))
printfn $"S={o.Side}, A={o.GetArea()}, P={o.Perimeter}"  // S=2, A=4, P=8
o.Move(deltaY = -2.0)
printfn $"Center {o.Center}"  // Center (1, -1)

Fields

2 kind of field: implicit or explicit

Implicit fields

As we've already seen, let bindings implicitly define fields in the class...

Explicit fields

Syntax: val [ mutable ] [ access-modify ] field-name : type-name

  • No initial value

  • public by default

  • can be compiled using a backing field:

    • val mutable a: int → public field

    • val a: int → internal field a@ + property a => a@

Field vs property

// Explicit fields readonly
type C1 =
    val a: int
    val b: int
    val mutable c: int
    new(a, b) = { a = a; b = b; c = 0 } // 💡 Constructor 2ndary "compact"

// VS readonly properties
type C2(a: int, b: int) =
    member _.A = a
    member _.B = b
    member _.C = 0

// VS auto-implemented property
type C3(a: int, b: int) =
    member val A = a
    member val B = b with get
    member val C = 0 with get, set

Explicit field vs implicit field vs property

Explicit fields are not often used:

  • Only for classes and structures

  • Need a [<ThreadStatic>] variable

  • Interaction with F♯ class of code generated without primary constructor

Implicit fields / let bindings are quite common, to define intermediate variable during construction.

Other use cases match auto-implemented properties:

  • Expose a value → member val

  • Expose a mutable "field" → member val ... with get, set

Structures

Alternatives to classes, but with more limited inheritance and recursion features

Same syntax as for classes, but with the addition of:

  • [<Struct>] attribute

  • Or struct...end block (more frequent)

type Point =
    struct
        val mutable X: float
        val mutable Y: float
        new(x, y) = { X = x; Y = y }
    end

let x = Point(1.0, 2.0)
PreviousType extensionsNextInterface

Last updated 1 month ago

Was this helpful?

Useful with native functions manipulating memory directly (Because fields order is preserved - see )

SharpLab