モジュール

F# のコンテキストで ''モジュール'' は、F# プログラムにおける値、型、関数値などの F# コードのグループです。 モジュールのコードをグループ化すると、関連するコードをまとめることができ、プログラム内で名前の競合を回避するのに役立ちます

構文

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

解説

F# モジュールは、do バインドにおける値、型、関数値、コードなどの F# コード コンストラクトのグループです。 静的メンバーのみを持つ共通言語ランタイム (CLR) クラスとして実装されます。 モジュール宣言には、ファイル全体がモジュールに含まれるかどうかに応じて、最上位レベルのモジュール宣言とローカル モジュール宣言の 2 種類があります。 最上位レベルのモジュール宣言には、モジュール内のファイル全体が含まれます。 最上位レベルのモジュール宣言は、ファイル内の最初の宣言としてのみ使用できます。

最上位レベルのモジュール宣言の構文では、省略可能な qualified-namespace は、モジュールを含む入れ子になった名前空間の名前のシーケンスです。 修飾された名前空間が事前に宣言されている必要はありません。

最上位レベルのモジュールで宣言をインデントする必要はありません。 ローカル モジュール内のすべての宣言はインデントする必要があります。 ローカル モジュール宣言では、そのモジュール宣言の下でインデントされた宣言のみがモジュールの一部になります。

最上位レベルのモジュール宣言または名前空間宣言で開始されていないコード ファイルの場合、ローカル モジュールを含むファイルの内容全体が、暗黙的に作成された最上位レベルのモジュールの一部になります。このモジュールには、拡張子のない、最初の文字が大文字に変換されたファイルと同じ名前が付けられます。 たとえば、次のようなファイルがあるとします。

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

このファイルは、このように記述されている場合と同じようにコンパイルされます。

module Program
let x = 40

ファイルに複数のモジュールがある場合は、モジュールごとにローカル モジュール宣言を使用する必要があります。 外側の名前空間が宣言されている場合、これらのモジュールはその外側の名前空間の一部になります。 外側の名前空間が宣言されていない場合、そのモジュールは暗黙的に作成された最上位レベルのモジュールの一部になります。 次のコード例には、複数のモジュールを含むコード ファイルが示されています。 コンパイラにより、Multiplemodules という名前の最上位レベルのモジュールが暗黙的に作成され、MyModule1MyModule2 はその最上位レベルのモジュールに入れ子になっています。

// 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)

1 つのプロジェクトまたは単一のコンパイルに複数のファイルがある場合、またはライブラリをビルドする場合は、ファイルの先頭に名前空間宣言またはモジュール宣言を含める必要があります。 F# コンパイラでは、プロジェクトまたはコンパイル コマンド ラインにファイルが 1 つしかなく、アプリケーションを作成する場合にのみ暗黙的にモジュール名が特定されます。

accessibility-modifier は、publicprivateinternal のいずれかにすることができます。 詳しくは、「アクセス制御」をご覧ください。 既定値はパブリックです。

モジュールでのコード参照

モジュール間で関数、型、および値を参照する場合は、修飾名を使用するか、モジュールを開く必要があります。 修飾名を使用する場合は、名前空間、モジュール、および必要なプログラム要素の識別子を指定する必要があります。 修飾されたパスの各部分は、次のようにドット (.) で区切ります。

Namespace1.Namespace2.ModuleName.Identifier

モジュールあるいは 1 つまたは複数の名前空間を開いて、コードを簡略化できます。 名前空間とモジュールを開く方法の詳細については、「インポート宣言: open キーワード」を参照してください。

次のコード例には、ファイルの末尾までのすべてのコードを含む最上位レベルのモジュールが示されています。

module Arithmetic

let add x y =
    x + y

let sub x y =
    x - y

同じプロジェクト内の別のファイルからこのコードを使用するには、次の例に示すように、修飾名を使用するか、モジュールを開いてから関数を使用します。

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

入れ子のモジュール

モジュールを入れ子にすることができます。 内部モジュールは、それらが新しいモジュールではなく内部モジュールであることを示す外部モジュール宣言がある限り、インデントする必要があります。 たとえば、次の 2 つの例を比較します。 次のコードでは、モジュール Z は内部モジュールです。

module Y =
    let x = 1

    module Z =
        let z = 5

しかし、次のコードでは、モジュール Z はモジュール Y の兄弟です。

module Y =
    let x = 1

module Z =
    let z = 5

次のコードでも、モジュール Z は兄弟モジュールです。これは、Y モジュール内に他の宣言がある限り、インデントされないためです。

module Y =
        let x = 1

    module Z =
        let z = 5

最後に、外部モジュールに宣言がなく、その直後に別のモジュール宣言がある場合、新しいモジュール宣言は内部モジュールと見なされますが、2 番目のモジュール定義が最初のものより遠い位置でインデントされていない場合は、コンパイラによって警告が表示されます。

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

警告が表示されないようにするには、内部モジュールをインデントします。

module Y =
    module Z =
        let z = 5

ファイル内のすべてのコードを 1 つの外部モジュールに配置し、複数の内部モジュールを使用する場合、外部モジュールでは等号を必要とせず、外部モジュールに含まれる内部モジュール宣言を含む宣言をインデントする必要はありません。 内部モジュール宣言内の宣言は、インデントする必要があります。 この場合のコードは次のようになります。

// 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

再帰的なモジュール

F# 4.1 では、含まれているすべてのコードを相互に再帰的に使用できるモジュールの概念が導入されています。 これは、module rec を使用して行われます。 module rec を使用すると、型とモジュール間で相互参照コードを記述できないという問題を軽減できます。 以下にこの例を示します。

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

例外 DontSqueezeTheBananaException とクラス Banana の両方が相互に参照していることに注目してください。 さらに、モジュール BananaHelpers とクラス Banana も相互に参照しています。 RecursiveModule モジュールから rec キーワードを削除した場合、これを F# で表現することはできません。

この機能は、F# 4.1 を使用して名前空間で利用することもできます。

関連項目