HOME Haskell のお勉強 download 書き込む

11. Module


プログラムがある程度 (数百行以上)大きくなるとソースコードや 名前空間 を分割したほうが 作成しやすくなります。特に名前空間を分割すると、関数名やデータ名が衝突するリスクを減らすことができます。 名前空間を分割は Haskell では Module で行います。

この文章では、プログラムを複数の Module に分割する方法について概説します。

1. Module の定義の仕方

Module の定義は、モジュール名、エクスポートするシンボルのリスト(exp-list) を書き、 その後に where をおいて本体を書きます。 exp-list が省略されたときは、本体で定義された すべてのデータ型、関数が エクスポートされます。Module 名は必ず大文字で始めます。

一般に、ソースファイル1つにつき Module 1つを書き、Module 名とファイル名を一致させます。 [code 1] に例として Util.hs を示します。 Util.hsModule Util を定義し、 3つの関数 (vpro, seqn, geozip) をエクスポートします。

[code 1]

01:     ---------------------
02:     -- utilities
03:     --------------------
04:     module Util (vpro, seqn, geozip) where
05:     
06:     -- it calculates vector inner product
07:     vpro :: [Double] ->  [Double] -> Double
08:     vpro ls1 ls2 = sum $ zipWith (*) ls1 ls2
09:     
10:     -- it calculates infinite sequence,
11:     --  whose initial term and next-term-calculating-function are a0 and f
12:     seqn :: a -> (a -> a) -> [a]
13:     seqn a0 f = a0 : seqn (f a0) f
14:     
15:     -- 
16:     geozip :: [Double] -> [Double] -> [[Double]]
17:     geozip ls0 ls1 = seqn ls0 (zipWith (*) ls1)

2. import の仕方

Module のインポートは import で行います。

import の書式は以下のように表されます。名前を指定してインポートすることもできます。

impdecl 	 -> 	 import [qualified] modid [as modid] [impspec]
	| 		(empty declaration)
impspec 	-> 	( import1 , ... , importn [ , ] ) 	(n>=0)
	| 	hiding ( import1 , ... , importn [ , ] ) 	(n>=0)
import 	-> 	var
	| 	tycon [ (..) | ( cname1 , ... , cnamen )] 	(n>=0)
	| 	tycls [(..) | ( var1 , ... , varn )] 	(n>=0)
cname 	-> 	var | con
以下の表に module Foo に名前 hogebar があったとき宣言の仕方によって インポートされる名前と、参照の仕方を示します。

宣言 インポートされる名前 参照の方法
import Foo hoge, bar hoge, Foo.hoge, bar, Foo.bar
import Foo () (何もインポートされない)
import Foo (hoge) hoge hoge, Foo.hoge
import qualified Foo () (何もインポートされない)
import Foo hiding () hoge, bar hoge, Foo.hoge, bar, Foo.bar
import Foo hiding (hoge) bar bar, Foo.bar
import qualified Foo hiding () hoge, bar Foo.hoge, Foo.bar
import qualified Foo hiding (hoge) bar Foo.bar
import Foo as F hoge, bar hoge, F.hoge, bar, F.bar
import Foo as F (hoge) hoge hoge, F.hoge
import qualified Foo as F hoge, bar F.hoge, F.bar

上に示したように、import にはいろいろなオプションがありますが qualified ... as がお勧めです。 qualified をつけると、インポートしたシンボルは必ず Module 名 (as を使って別名を定義したときは別名)で修飾しなければ ならなくなるので、名前空間が汚染されるのを防ぐことができます。

[code 2] は Module Util ([code 1]) をインポートしている Module です。Util の別名を U とし、 Util で定義された geozipU.geozip として参照します(14, 20 行目)。

[code 2]

01:     ------------------------------------------------
02:     -- This module makes matrix of S(x^i)
03:     -- and vector  [S(y), S(xy), S(x^2 y) ... S(x^n y)]
04:     --------------------------------------------------
05:     
06:     module MakeMat (make_sx, make_vxy) where
07:     
08:     import qualified Util as U
09:     
10:     -- making [[n, S(x), .. S(x^n)], [S(x) ... S(x^(n+1))], ... [S(x^n), ... S(x^2n)]]
11:     make_sx :: Int -> [Double] -> [[Double]]
12:     make_sx n xs = mat 0
13:      where
14:          lsum = map sum $ U.geozip (take (length xs) [1.0, 1.0 ..]) xs
15:          mat i | i==n+1 = []
16:                | otherwise = (take (1+n) (drop i lsum)):(mat (i+1))
17:     
18:     -- making [S(y), S(xy), S(x^2 y) ... S(x^n y)]
19:     make_vxy :: Int -> [Double] -> [Double] -> [Double]
20:     make_vxy n xs ys = map sum $ take (n+1) $ U.geozip ys xs

3. 複数のソースファイルのコンパイル

ghc を使って複数のソースファイルをコンパイルするときは --make フラグを使うと便利です。 --make フラグを使うとコンパイルしていない Module だけコンパイルしてリンクしてくれます。 例えば、
これ (lfit2.lzh)を解凍してできるコードをコンパイルして lfit2.exe という実行形式を作るときは 以下のようにします。
>ghc --make Main.hs -o lfit2.exe
ちなみに、lfit2 はに取り上げた最小二乗法プログラムの改良版で、 パラメータの誤差を算出してくれます。
>lfit2 2 a.dat
c0 = 2.531727598666521 (+-) 4.907248623738801e-2
c1 = -0.5005644499197217 (+-) 1.0695009355167438e-4
c2 = -2.9990377224373255e-2 (+-) 1.0217445932292105e-8

4. 終わりに

Haskell の Module について簡単に述べてみました。

Haskell98 には他にもいろいろと書いてありますが、とりあえずここに書いてあることだけ 押えておけば十分だと思います。