Modules

Dans le contexte de F#, un module est un regroupement de code F#, notamment des valeurs, des types et des valeurs de fonction, dans un programme F#. Le regroupement de code en modules vous permet de centraliser le code connexe et d’éviter les conflits de nom dans votre programme.

Syntaxe

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

Notes

Un module F# est un regroupement de constructions de code F#, notamment des types, des valeurs, des valeurs de fonction et du code dans des liaisons do. Il est implémenté comme une classe CLR (Common Language Runtime) qui a seulement des membres statiques. Il existe deux types de déclarations de module, selon que le fichier entier est inclus dans le module : une déclaration de module de niveau supérieur et une déclaration de module locale. Une déclaration de module de niveau supérieur comprend le fichier entier dans le module. Une déclaration de module de niveau supérieur peut apparaître seulement comme la première déclaration dans un fichier.

Dans la syntaxe de la déclaration de module de niveau supérieur, l’argument qualified-namespace facultatif est la séquence de noms d’espaces de noms imbriqués qui contient le module. L’espace de noms qualifié n’a pas besoin d’être déclaré au préalable.

Vous n’avez pas besoin de mettre en retrait les déclarations dans un module de niveau supérieur. Vous devez mettre en retrait toutes les déclarations dans les modules locaux. Dans une déclaration de module local, seules les déclarations qui sont mises en retrait sous cette déclaration de module font partie du module.

Si un fichier de code ne commence pas par une déclaration de module de niveau supérieur ou une déclaration d’espace de noms, l’ensemble du contenu du fichier, y compris les modules locaux, fait partie d’un module de niveau supérieur créé implicitement qui porte le même nom que le fichier, sans l’extension, avec la première lettre en majuscule. Par exemple, prenons le fichier suivant.

// In the file program.fs.
let x = 40

Ce fichier est compilé comme s’il était écrit de cette manière :

module Program
let x = 40

Si vous avez plusieurs modules dans un fichier, vous devez utiliser une déclaration de module locale pour chaque module. Si un espace de noms englobant est déclaré, ces modules font partie de l’espace de noms englobant. Si un espace de noms englobant n’est pas déclaré, les modules font partie du module de niveau supérieur créé implicitement. L’exemple de code suivant montre un fichier de code qui contient plusieurs modules. Le compilateur crée implicitement un module de niveau supérieur nommé Multiplemodules, et MyModule1 et MyModule2 sont imbriqués dans ce module de niveau supérieur.

// In the file multiplemodules.fs.
// MyModule1
module MyModule1 =
    // Indent all program elements within modules that are declared with an equal sign.
    let module1Value = 100

    let module1Function x =
        x + 10

// MyModule2
module MyModule2 =

    let module2Value = 121

    // Use a qualified name to access the function.
    // from MyModule1.
    let module2Function x =
        x * (MyModule1.module1Function module2Value)

Si vous avez plusieurs fichiers dans un projet ou dans une seule compilation, ou si vous générez une bibliothèque, vous devez ajouter une déclaration d’espace de noms ou une déclaration de module en haut du fichier. Le compilateur F# détermine implicitement un nom de module uniquement s’il y a un seul fichier dans un projet ou une ligne de commande de compilation et que vous créez une application.

L’argument accessibility-modifier peut être : public, private ou internal. Pour obtenir plus d'informations, voir Contrôle d'accès. La valeur par défaut est public.

Référencement du code dans les modules

Quand vous référencez des fonctions, des types et des valeurs d’un autre module, vous devez utiliser un nom qualifié ou ouvrir le module. Si vous utilisez un nom qualifié, vous devez spécifier les espaces de noms, le module et l’identificateur de l’élément de programme souhaité. Vous séparez chaque partie du chemin qualifié par un point (.), de la façon suivante.

Namespace1.Namespace2.ModuleName.Identifier

Vous pouvez ouvrir le module, ou un ou plusieurs espaces de noms, pour simplifier le code. Pour plus d’informations sur l’ouverture d’espaces de noms et de modules, consultez Déclarations d’importation : mot clé open.

L’exemple de code suivant montre un module de niveau supérieur qui contient tout le code jusqu’à la fin du fichier.

module Arithmetic

let add x y =
    x + y

let sub x y =
    x - y

Pour utiliser ce code dans un autre fichier dans le même projet, vous utilisez des noms qualifiés ou ouvrez le module avant d’utiliser les fonctions, comme illustré dans les exemples suivants.

// Fully qualify the function name.
let result1 = Arithmetic.add 5 9
// Open the module.
open Arithmetic
let result2 = add 5 9

Modules imbriqués

Les modules peuvent être imbriqués. Les modules internes doivent être mis en retrait jusqu’aux déclarations de module externe pour indiquer qu’il s’agit de modules internes, et non de nouveaux modules. Par exemple, comparez les deux exemples suivants. Le module Z est un module interne dans le code suivant.

module Y =
    let x = 1

    module Z =
        let z = 5

Le module Z est un frère du module Y dans le code suivant.

module Y =
    let x = 1

module Z =
    let z = 5

Le module Z est également un module frère dans le code suivant, car il n’est pas mis en retrait au niveau des autres déclarations du module Y.

module Y =
        let x = 1

    module Z =
        let z = 5

Enfin, si le module externe n’a pas de déclarations et est immédiatement suivi d’une autre déclaration de module, la nouvelle déclaration de module est censée être un module interne, mais le compilateur vous avertit si la deuxième définition de module n’est pas mise en retrait plus loin que la première.

// This code produces a warning, but treats Z as a inner module.
module Y =
module Z =
    let z = 5

Pour éliminer l’avertissement, mettez en retrait le module interne.

module Y =
    module Z =
        let z = 5

Si vous voulez que tout le code d’un fichier se trouve dans un seul module externe et que vous voulez des modules internes, le module externe ne nécessite pas le signe égal, et les déclarations, y compris les déclarations de module interne, qui vont dans le module externe n’ont pas besoin d’être mises en retrait. Les déclarations à l’intérieur des déclarations de module interne doivent être mises en retrait. Le code suivant montre ce cas.

// The top-level module declaration can be omitted if the file is named
// TopLevel.fs or topLevel.fs, and the file is the only file in an
// application.
module TopLevel

let topLevelX = 5

module Inner1 =
    let inner1X = 1
module Inner2 =
    let inner2X = 5

Modules récursifs

F# 4.1 introduit la notion de modules autorisant que l’ensemble du code contenu soit mutuellement récursif. Cette opération est effectuée via module rec. L’utilisation de module rec peut soulager certaines douleurs dans l’impossibilité d’écrire du code référentiel mutuellement entre les types et les modules. Voici un exemple de ceci :

module rec RecursiveModule =
    type Orientation = Up | Down
    type PeelState = Peeled | Unpeeled

    // This exception depends on the type below.
    exception DontSqueezeTheBananaException of Banana

    type Banana(orientation : Orientation) =
        member val IsPeeled = false with get, set
        member val Orientation = orientation with get, set
        member val Sides: PeelState list = [ Unpeeled; Unpeeled; Unpeeled; Unpeeled] with get, set

        member self.Peel() = BananaHelpers.peel self // Note the dependency on the BananaHelpers module.
        member self.SqueezeJuiceOut() = raise (DontSqueezeTheBananaException self) // This member depends on the exception above.

    module BananaHelpers =
        let peel (b: Banana) =
            let flip (banana: Banana) =
                match banana.Orientation with
                | Up ->
                    banana.Orientation <- Down
                    banana
                | Down -> banana

            let peelSides (banana: Banana) =
                banana.Sides
                |> List.map (function
                             | Unpeeled -> Peeled
                             | Peeled -> Peeled)

            match b.Orientation with
            | Up ->   b |> flip |> peelSides
            | Down -> b |> peelSides

Notez que l’exception DontSqueezeTheBananaException et la classe Banana font référence les unes aux autres. En outre, le module BananaHelpers et la classe Banana font également référence les uns aux autres. Vous ne pouvez pas l’exprimer en F# si vous avez supprimé le mot clé rec du module RecursiveModule.

Cette fonctionnalité est également possible dans des espaces de noms avec F# 4.1.

Voir aussi