Haskell のプログラムはモジュール ( module )の集合です。 Haskell のモジュールは名前空間の制御と抽象データ型の生成というふたつの目 的のためにあります。
トップレベルのモジュールはこれまで議論したさまざまな宣言をすべて含みます。 結合性宣言、データ宣言と型宣言、クラス宣言とインスタンス宣言、型シグネチャ、 関数定義、それに、パターン束縛です。インポート宣言(これから簡単に説明し ます)が最初にこなければならないことをのぞけば、宣言はどのような順序で出 現してもかまいまっせん。(トップレベルのスコープは相互再帰的です。)
Haskell のモジュールは比較的保守的にできています。モジュールの名前空間は 平坦です。また、モジュールは「第一級」の対象ではありません。モジュールの 名前は英数字で、最初の文字は大文字でなければなりません。Haskell のモジュー ルと(典型的に)サポートされているファイルシステムの間には正式な結び付きは ありません。とくに、モジュール名とファイル名とは正式には結びつきません。 また、ふたつ以上のモジュールを一つのファイルに含むことができます。(ひと つのモジュールを複数のファイルに分ることもできます。) もちろん、特定の実 装でモジュールとファイル名が関連するような習慣を採用するのがふつうでしょ う。
技術的な観点では、モジュールというのは module というキーワード
ではじまるひとつの大きな宣言ということができます。つぎのは
Tree というモジュールの例です。
module Tree ( Tree(Leaf,Branch), fringe ) where
data Tree a = Leaf a | Branch (Tree a) (Tree a)
fringe :: Tree a -> [a]
fringe (Leaf x) = [x]
fringe (Branch left right) = fringe left ++ fringe right
この Tree 型と fringe 関数は
2.2.1 節で例としてとりあ
げましたので、もうおなじみのはずですね。[ where というキーワー
ドがあるので、レイアウトがモジュールのトップレベルで有効になります。それ
ゆえ、宣言はすべておなじカラム位置(典型的には第一カラム)に配置されます。
また、モジュール名はこの型の名前とおなじですが、これは許されていることに
注意してください。]
このモジュールは明示的に Tree、 Leaf、 Branch、 および fringe をエクスポートしています。もし、この module キーワードのあとに続く、エクスポートリストがなければ、こ のモジュールのトップレベルで束縛されている名前はすべてエクスポートされま す。(上の例では、すべてが明示的にエクスポートされています。ですから効果 としてはエクスポートリストがない場合とおなじです。) 型の名前とその構築子 は、Tree(Leaf,Branch) のようにひとつのグループにまとめられます。 これを簡略して、Tree(..) のように書くこともできます。構築子のサ ブセットをエクスポートすることも可能です。エクスポートリストのなかの名前 は当該のエクスポートモジュールの局所名である必要はありません。スコープ内 の名前であればこのエクスポートリストに入れることができます。
これで、Tree モジュールはなにか別のモジュールにインポート
することができます。
module Main (main) where
import Tree ( Tree(Leaf,Branch), fringe )
main = print (fringe (Branch (Leaf 1) (Leaf 2)))
インポートされたりエクスポートされたりするモジュールのさまざまな項目のこ
とを エンティティ と呼びます。インポート宣言における明示的なイ
ンポートリストを省略すると Tree モジュールからエクスポートされ
たすべてのエンティティがインポートされることに注目してください。
モジュールの名前空間に直接インポートされた名前はあきらかな問題をはらんで
います。もし、インポートされた複数のモジュールが同じ名前の別のエンティティ
を含んでいたらどうなるでしょう。Haskell ではこのもんだいを修飾された
名前 ( qualified name ) を使うことで解決します。インポート宣
言で、qualified キーワードをつかい、インポートされた名前がイン
ポートされたモジュール名を接頭辞として使うようにします。これらの接頭辞の
あとに 「 . 」文字を空白をいれずに続けます。[接頭辞は字句構文の
一部です。すなわち、A.x および A . x は全く
別ものです。前者は修飾された名前であり、後者は中置関数の 「 .
」です。] たとえば、先程の Tree モジュールを使ってみましょう。
module Fringe(fringe) where
import Tree(Tree(..))
fringe :: Tree a -> [a] -- A different definition of fringe
fringe (Leaf x) = [x]
fringe (Branch x y) = fringe x
module Main where
import Tree ( Tree(Leaf,Branch), fringe )
import qualified Fringe ( fringe )
main = do print (fringe (Branch (Leaf 1) (Leaf 2)))
print (Fringe.fringe (Branch (Leaf 1) (Leaf 2)))
Haskell のプログラマのなかにはすべてのインポートされた名前について、それ
ぞれの由来がはっきりわかるように、修飾子をつかうことを好むひともいるでしょ
う。一方、みじかい名前を好むので、修飾子は必要最小限にしようとするプログ
ラマもいることでしょう。
修飾子はおなじ名前をもつ別のエンティティ間の衝突を解決するために使用しま す。しかし、ふたつ以上のモジュールから同じエンティティがインポートされた場 合どうなるでしょう。幸いこのような名前のぶつかりは許されています。ひとつ のエンティティが別の経路でインポートされても衝突はおこしません。コンパイ ラが別のモジュールからのエンティティが実際に同じものかどうかを判別します。
名前空間の制御のほかに、モジュールは Haskell で抽象データ型
( ADT ) を構築する唯一の方法です。たとえば、ADT の特徴は、表現型
( representation type ) が隠蔽されるということです
が、これは ADT 上のすべての演算がその表現型には依存せずに、抽象レベルで
実行されるということです。例をあげましょう。Tree 型は単純なので
ふつは抽象型にはしませんが、これに対応する ADT は次のような演算をふくみ
ます。
data Tree a -- just the type name
leaf :: a -> Tree a
branch :: Tree a -> Tree a -> Tree a
cell :: Tree a -> a
left, right :: Tree a -> Tree a
isLeaf :: Tree a -> Bool
モジュールはこれを支援します。
module TreeADT (Tree, leaf, branch, cell,
left, right, isLeaf) where
data Tree a = Leaf a | Branch (Tree a) (Tree a)
leaf = Leaf
branch = Branch
cell (Leaf a) = a
left (Branch l r) = l
right (Branch l r) = r
isLeaf (Leaf _) = True
isLeaf _ = False
エクスポートリストに Tree という型名のみ(すなわち構築子がないと
いうこと)があらわれていることに注目してください。ということは、
Leaf や Branch はエクスポートされないということです。
そして、木を構成したり、構成要素をとりだしたりする唯一の方法は、いくつか
の(抽象)演算をつかうことです。もちろん、この情報の隠蔽の利点はあとで表現
型をこの型の利用者に影響をあたえることなく、変更できるというこ
とです。
ここで、このモジュールシステムのその他の側面について手短に説明しましょう。 詳細についてはレポートを参照してください。