Formation F#
  • Intro
  • Bases
    • Le F♯, c'est quoi ?
    • Syntaxe
    • Premiers concepts
    • 🍔 Quiz
  • Fonctions
    • Signature
    • Fonctions
    • Fonctions standard
    • Opérateurs
    • Fonctions : compléments
    • 🍔 Quiz
    • 📜 Récap’
  • Types composites
    • Généralités
    • Tuples
    • Records
    • Unions
    • Enums
    • Records anonymes
    • Types valeur
    • 🍔 Quiz
  • Types : Compléments
    • Type unit
    • Génériques
    • Types flexibles
    • Unités de mesure
    • Conversion
    • Exceptions F#
  • Pattern matching
    • Patterns
    • Match expression
    • 🚀 Active Patterns
    • 📜 Récap’
  • Collections
    • Vue d'ensemble
    • Types
    • Fonctions génériques
    • Fonctions spécifiques
    • 🍔 Quiz
    • 📜 Récap’
  • Programmation asynchrone
    • Workflow asynchrone
    • Interop avec la TPL .NET
    • 📜 Récap’
  • Types monadiques
    • Type Option
    • Type Result
    • Smart constructor
    • 🚀 Computation expression (CE)
    • 🚀 CE - Fondements théoriques
    • 📜 Récap’
  • Module & namespace
    • Vue d'ensemble
    • Namespace
    • Module
    • 🍔 Quiz
    • 📜 Récap’
  • Orienté-objet
    • Introduction
    • Membres
    • Extensions de type
    • Classe, structure
    • Interface
    • Expression objet
    • Recommandations
  • 🦚 Aller plus loin
Propulsé par GitBook
Sur cette page
  • Syntaxe
  • Module top-level
  • Module top-level implicite
  • Module local
  • Contenu d'un module
  • Équivalence module / classe statique
  • Module imbriqué
  • Module top-level vs local
  • Module récursif
  • Annotation d'un module
  • [<RequireQualifiedAccess>]
  • [<AutoOpen>]
  • AutoOpen, RequireQualifiedAccess ou rien ?
  • Module et Type
  • Module compagnon d'un type
  • Module wrappant un type
  • Top-level module ou namespace

Cet article vous a-t-il été utile ?

Modifier sur GitHub
  1. Module & namespace

Module

Syntaxe

// Top-level module
module [accessibility-modifier] [qualified-namespace.]module-name
declarations

// Local module
module [accessibility-modifier] module-name =
    declarations

accessibility-modifier : restreint l'accessibilité → public (défaut), internal (assembly), private (parent)

Le nom complet ([namespace.]module-name) doit être unique → 2 fichiers ne peuvent pas déclarer des modules de même nom

Module top-level

  • Doit être déclaré en 1er dans un fichier ❗

  • Contient tout le reste du fichier

    • Contenu non indenté

    • Ne peut pas contenir de namespace

  • Peut être qualifié = inclus dans un namespace parent (existant ou non)

Module top-level implicite

  • Si fichier sans module/namespace top-level

  • Nom du module = nom du fichier

    • Sans l'extension

    • Avec 1ère lettre en majuscule

    • Ex : program.fs → module Program

Module local

Syntaxe similaire au let mais ne pas oublier :

  • Le signe = après le nom du module local ❗

  • D'indenter tout le contenu du module local

    • Non indenté = ne fait pas partie du module local

Contenu d'un module

Un module, local comme top-level, peut contenir :

  • Types et sous modules locaux

  • Valeurs, fonctions

Différence : l'indentation du contenu

  • Module top-level : contenu non indenté

  • Module local : contenu indenté

Équivalence module / classe statique

module MathStuff =
    let add x y  = x + y
    let subtract x y = x - y

Ce module F♯ est équivalent à la classe statique suivante :

public static class MathStuff
{
    public static int add(int x, int y) => x + y;
    public static int subtract(int x, int y) => x - y;
}

Module imbriqué

Comme en C♯ et les classes, les modules F♯ peuvent être imbriqués

module Y =
    module Z =
        let z = 5

printfn "%A" Y.Z.z

☝ Notes :

  • Intéressant avec module imbriqué privé pour isoler/regrouper

  • Sinon, préférer une vue aplanie

Module top-level vs local

Propriété
Top-level
Local

Qualifiable

✅

❌

Signe =

+ Contenu indenté

❌

✅ ❗

  • Module top-level → 1er élément déclaré dans un fichier

  • Sinon (après un module/namespace top-level) → module local

Module récursif

Même principe que namespace récursif → Pratique pour qu'un type et un module associé se voient mutuellement

☝ Recommandation : limiter au maximum la taille des zones récursives

Annotation d'un module

2 attributs influencent l'usage d'un module

[<RequireQualifiedAccess>]

→ Force l'usage qualifié des éléments d'un module → Empêche l'import du module

  • 💡 Pratique pour éviter le shadowing pour des noms communs : add, parse...

  • ☝️ Il n'y a pas d'attribut équivalent pour les méthodes statiques d'un type :

    • Par défaut, on utilise l'accès qualifié, y compris à l'intérieur du type, alors qu'à l'intérieur d'un module on n'accède directement aux autres fonctions, la qualification n'étant pas possible (sauf peut-être en rendant le module récursif 🤷)

    • L'accès direct est possible en important le type à l'aide du mot clé open type.

[<AutoOpen>]

Import du module en même temps que le module/namespace parent

  • 💡 Pratique pour "monter" valeurs/fonctions au niveau d'un namespace (un namespace ne pouvant pas en contenir normalement)

💡On emploie couramment AutoOpen pour mieux organiser un module, en regroupant des éléments dans des modules enfants → à condition qu'ils restent de taille raisonnable, sinon il vaudrait mieux considérer de les répartir dans différents fichiers → on peut combiner cela avec le fait de rendre certains modules private pour cacher l'ensemble de son contenu au code appelant tout en gardant ce contenu accessible directement au reste du module courant.

👉 Avoir un module AutoOpen à l'intérieur d'un module RequireQualifiedAccess ne fait sens que si le module est private.

AutoOpen, RequireQualifiedAccess ou rien ?

Soit un type Cart avec son module compagnon Cart → Comment appeler la fonction qui ajoute un élément au panier ?

  • addItem item cart → [<RequireQualifiedAccess>] intéressant pour forcer à avoir Cart.addItem dans le code appelant et lever toute ambiguïté sur le containeur (Cart) dans lequel on ajoute l'élément

  • addItemToCart item cart → addItemToCart est self-explicit, Cart.addItemToCart pléonastique. → [<AutoOpen>] intéressant pour éviter de devoir importer le module

Si le module Cart contient d'autres fonctions de ce type, mieux vaudrait leur appliquer toute la même convention de nommage.

Module et Type

Un module sert typiquement à regrouper des fonctions agissant sur un type de donnée bien spécifique.

2 styles, selon localisation type / module :

  • Type défini avant le module → module compagnon

  • Type défini dans le module

Module compagnon d'un type

  • Style par défaut - cf. List, Option, Result...

  • Bonne interop autres langages .NET

  • Module peut porter le même nom que le type

type Person = { FirstName: string; LastName: string }

module Person =
    let fullName person = $"{person.FirstName} {person.LastName}"

let person = { FirstName = "John"; LastName = "Doe" }   // Person
person |> Person.fullName // "John Doe"

Module wrappant un type

  • Type défini à l'intérieur du module

  • On peut nommer le type T ou comme le module

module Person =
    type T = { FirstName: string; LastName: string }

    let fullName person = $"{person.FirstName} {person.LastName}"

let person = { FirstName = "John"; LastName = "Doe" }   // Person.T ❗
person |> Person.fullName // "John Doe"

Recommandé pour améliorer encapsulation

  • Constructeur du type private

  • Module contient un smart constructor

module Person =
    type T = private { FirstName: string; LastName: string }

    let create first last =
        if System.String.IsNullOrWhiteSpace first
        then Error "FirstName required"
        else Ok { FirstName = first; LastName = last }

    let fullName person =
        $"{person.FirstName} {person.LastName}".Trim()

Person.create "" "Doe"                                // Error "LastName required"
Person.create "Joe" "" |> Result.map Person.fullName  // Ok "Joe"

Top-level module ou namespace

  • Préférer un namespace pour :

    • 1 ou plusieurs types, avec quelques modules compagnons

  • Dans les autres cas, préférer un top-level module, pour gagner un niveau d'indentation.

PrécédentNamespaceSuivant🍔 Quiz

Dernière mise à jour il y a 1 an

Cet article vous a-t-il été utile ?

Cf.

️ Pollue le scope courant

️ Portée de AutoOpen Quand on n'a pas importé le module/namespace parent, AutoOpen est sans effet : pour accéder au contenu du module enfant, la qualification comprend non seulement le module/namespace parent mais également le module enfant : → Parent.childFunction ❌ → Parent.Child.childFunction ✅

Interop avec C# → Cf.

⚠️
⚠️
sharplab.io
docs.microsoft.com/.../fsharp/style-guide/conventions#organizing-code