モジュール
モジュールとは、定義の集合に名前を付ける機能のことです。
関連した定義だけをまとめることによって
プログラムの独立性を高めることが出来ます。
F#のモジュールシステムはOCaml譲りですが
 ・F#用の構文が推奨されている
 ・Functorなどの一部の機能は実装されていない
 ・OCamlとは違い、モジュール名は大文字/小文字どちらでもOK
といった違いがあるようです。(他にもありそうです)
モジュール名に小文字は使えますが慣習上大文字から始めたほうがいいように思います。
モジュールの定義にはF#版/OCaml版の2種類の構文が使えます。
- F#版のモジュールの構文
- モジュール定義:= begin [モジュール要素]* end
モジュール要素:=
	| let式
	| type定義
	| 例外定義
	| モジュール定義
	| モジュールの省略名の定義
	| import宣言
        | コンパイラディレクティブ
モジュールの省略名の定義:=
	module 省略名 = モジュール名
- OCaml互換のモジュールの構文
- モジュール定義:= struct [モジュール要素]* end
(他は共通)
こちらの構文はml互換用に用意されているものでF#での使用は推奨されていません
なお、軽量構文の場合begin,endは省略出来ます
それでは、実際にモジュールを作ってみます
モジュールの作成
module A = begin
    let a = 1
    end;;
//F#コードでは使用されないml互換の構文    
module B = struct
    let a = 2
    end;;
//モジュールへのアクセスにはドットを用いる
printfn "module A's a = %d\nmodule B's a = %d" A.a B.a;;
モジュールの作成(軽量構文)
module A =
    let a = 1
module B =
    let a = 2
//モジュールへのアクセスにはドットを用いる
printfn "module A's a = %d\nmodule B's a = %d" A.a B.a
この例では2つのモジュールA,Bを定義しています。
モジュールの中で定義されているものへアクセスするには
この例のようにドットを用います。
他の形式のモジュール定義
//型の定義。大文字のモジュール名ももちろんOK
module D = begin type A = int end;;
//例外の定義
module E = begin exception EX end;;
//モジュールの省略名の定義
module M = Microsoft.FSharp.Collections.Seq;;
//これは不可。System.Windows.Formsはモジュールではなく名前空間(後に説明)
//module WF = System.Windows.Forms
//省略名の利用
M.map ((+)1) [1..10] |> printfn "%A";;</code>
他の形式のモジュール定義(軽量構文)
//型の定義
module D = type A = int
//例外の定義
module E = exception EX
//モジュールの省略名の定義
module M = Microsoft.FSharp.Collections.Seq
//これは不可。System.Windows.Formsはモジュールではなく名前空間(後に説明)
//module WF = System.Windows.Forms
//省略名の利用
M.map ((+)1) [1..10] |> printfn "%A"
続いて、型、例外、省略名を定義してみた例です。
これらは、一つのモジュールに複合して入れることが出来ます。
モジュールの中に色々と定義
module Mix = begin
    let a = 10
    type T = int
    exception E
end
モジュールの中に色々と定義(軽量構文)
module Mix =
    let a = 10
    type T = int
    exception E
また、モジュールは入れ子に出来ます
モジュールの入れ子
module A = begin
    module B = begin
        let b = 5
    end
end;;
printfn "%d" (A.B.b);;
モジュールの入れ子(軽量構文)
module A = 
    module B = 
        let b = 5
printfn "%d" (A.B.b)
モジュールに対して毎回ドットを付けてアクセスするのが
面倒な場合は、モジュールをopenすることができます。
(定義で言えば、import宣言の所)
モジュールのopen
module A = begin
    let a = 6
end;;
open A;;
printfn "%d" a;;
モジュールのopen
module A =
    let a = 6
open A
printfn "%d" a
この例ではAというモジュールを定義していますが
open Aとしたことによって
ドットを付けなくても
直接Aの中のaにアクセス出来てしまうわけです。
これは、直接モジュールの中で使用できます。
この場合、モジュールの外側にはopenの影響はありません。
モジュール内でopen
module L = begin
    let mymap f list = [for i in list -> f i]
    end;;
module A = begin
    open L
    let a = mymap ((+)1) [1..10]
end;;
A.a |> printfn "%A";;
//mymap ((+)1) [1..10] |> printfn "%A" //error. open Lはmodule Aの中でのみ有効
モジュール内でopen(軽量構文)
module L =
    let mymap f list = [for i in list -> f i]
module A =
    open L
    let a = mymap ((+)1) [1..10]
A.a |> printfn "%A";;
//mymap ((+)1) [1..10] |> printfn "%A" //error. open Lはmodule Aの中でのみ有効
色々バッティングしそうな名前の関数が定義されているため推奨はされていませんでしたが以前はListをopenすることができました。
しかし現在は[<RequireQualifiedAccess>]という属性がListに付与されているため
リストはopenせず直接List.mapとListで修飾してから使わなければならなくなっています。
このプログラムではopenの性質を調べるためにmymap関数(List.mapと同様のもの)を定義して
モジュールAの中でopenしています。Aの中ではLで修飾せずにmymapが使えていますが
Aの外側(トップレベル)ではmymapが使えないことが確認できます。
さらに、モジュールを自動的にオープンしてくれる
AutoOpen属性(Microsoft.FSharp.Core.AutoOpenAttribute)
を付けることが出来ます。
モジュールを入れ子にしてAutoOpenの動作を
確認したのが次のプログラムです。
A2のパターンは自分にとっては少し予想外の動作でした。
AutoOpen属性
module A1 = begin
    let a1 = 1
    module B1 = begin let b1 = 11 end
end;;
module A2 = begin
    let a2 = 2
    [<AutoOpen>]
    module B2 = begin let b2 = 22 end
end;;
[<AutoOpen>]
module A3 = begin
    let a3 = 3
    module B3 = begin let b3  = 33 end
end;;
[<AutoOpen>]
module A4 = begin
    let a4 = 4
    [<AutoOpen>]
    module B4 = begin let b4 = 44 end
end;;
printfn "%d,%d" (A1.a1) (A1.B1.b1);;
printfn "%d,%d" (A2.a2) (A2.B2.b2);;
//printfn "%d,%d" (A2.a2) (A2.b2);;   //これは動作しないようです
printfn "%d,%d" a3 (B3.b3);;
printfn "%d,%d" a4 b4;;
AutoOpen属性(軽量構文)
module A1 = 
    let a1 = 1
    module B1 = let b1 = 11
module A2 = 
    let a2 = 2
    []
    module B2 = let b2 = 22
[]
module A3 = 
    let a3 = 3
    module B3 = let b3  = 33
[]
module A4 = 
    let a4 = 4
    []
    module B4 = let b4 = 44
printfn "%d,%d" (A1.a1) (A1.B1.b1)
printfn "%d,%d" (A2.a2) (A2.B2.b2)
//printfn "%d,%d" (A2.a2) (A2.b2)   //これは動作しないようです
printfn "%d,%d" a3 (B3.b3)
printfn "%d,%d" a4 b4
AutoOpenしたモジュールは
モジュール名の修飾なしにアクセスすることが
出来ることが確認できます。