Page Top

今回は… anchor.png

モジュールについてお話します。

モジュールを利用する利点は、ファイルを分割することにあります。
似たような種類の関数や、変数などの定義をまとめておくための仕組みです。
下のサンプルコードでは、関数をモジュールにまとめて定義しています。

D言語ではモジュールはファイルと1:1対応となっています。つまり、モジュール名=ファイル名です。
ただし、モジュール名には.dという拡張子は必要ありません。

いままでは特に指定しなくてもファイル名が直接モジュール名として判定されてきました。
しかし、main関数のあるファイル以外には、モジュールを明示的に指定してやるとよいでしょう。
詳しくは述べませんが、そうしないと、たとえばdmdのコンパイラスイッチ-Iを指定した場合などに不都合な場合があるためです。

Page Top

今回のミソ anchor.png

  • ファイルの一番最初に以下のようにすることで明示的にモジュール名を指定する。ここでのXXXXはファイル名から.dを取り除いたもの。
    module XXXX;
  • モジュール内で定義されたファイルスコープの変数や関数は他のモジュールからは使うことができない。
  • 逆に、グローバルスコープの変数や関数は他のモジュールで使うことができる。
Page Top

サンプルコード anchor.png

filesample1401.d
Everything is expanded.Everything is shortened.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 
-
!
 
 
-
|
!
-
|
!
 
-
|
!
-
|
!
 
-
|
!
-
|
!
// モジュールの宣言の前に書いていいものはコメント文と、空白文字、改行、
// #!から始まる文だけ。#!から始まるこれはこの講座では詳しく述べません。
 
module sample1401;
 
// 特に何も指定しない場合グローバルスコープ。
// 外部から利用することができる。
int add(int a, int b)
{
    return a+b;
}
 
// privateを指定した場合はファイルスコープ。このファイル内からしか
// アクセスすることができない。
private int mul(int a, int b)
{
    return a*b;
}
 
// publicはグローバルスコープ。
// このモジュールがいからもアクセスすることができる。
public int squareSum(int a, int b)
{
    return add(mul(a,b), mul(a,b));
}

*1

Page Top

実行結果 anchor.png

1
$ dmd -c sample1401.d
Page Top

まとめ anchor.png

今回のだけではあまりありがたみがありませんね。
このモジュールという概念は、importと組み合わせることで、ファイルを分割することに役立ちます。

そんなimportの使い方は次回。
それでは次へどうぞ~

Page Top

#02 - インポート anchor.png

Page Top

今回は… anchor.png

前回のmoduleで定義されたモジュールをimportします。
いままで

import std.stdio;

等としていたのもすべてモジュールを読み込むために行っていたものなのです。

importでモジュールを読み込むと、読み込んだモジュール内で、グローバルスコープとして定義されたものを、importしたモジュールの中で利用することができるようになります。

Page Top

今回のミソ anchor.png

  • モジュールを読み込むにはimportを使って次のようにします。
    import modulename;
  • importされた関数はそのファイルで使うことができる。
Page Top

サンプルコード anchor.png

filesample1402p.d
Everything is expanded.Everything is shortened.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
 
 
 
-
-
|
|
|
!
|
-
|
|
|
!
|
-
|
|
!
|
!
import std.stdio;
import sample1401;
 
int main(char[][] args)
{
    // std.stdioモジュールで、グローバルスコープに定義された関数
    // writefln 関数を使用します。
    // sample1401モジュールで、グローバルスコープに定義された関数
    // add 関数を使用します。
    writeln(add(10, 3));
    
    // std.stdioモジュールで、グローバルスコープに定義された関数
    // writefln 関数を使用します。
    // sample1401モジュールで、グローバルスコープに定義された関数
    // squareSum 関数を使用します。
    writeln(squareSum(10,5));
    
    // ファイルスコープの関数にはアクセスすることができません。
    // 下の文のコメントアウトをはずすとコンパイルできなくなります。
    //writefln(mul(4,7));
    
    return 0;
}
+  Tango用はこちら
filesample1402t.d
Everything is expanded.Everything is shortened.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
 
 
 
-
-
|
|
|
!
|
-
|
|
|
!
|
-
|
|
!
|
!
import tango.io.Stdout;
import sample1401;
 
int main(char[][] args)
{
    // std.stdioモジュールで、グローバルスコープに定義された関数
    // writefln 関数を使用します。
    // sample1401モジュールで、グローバルスコープに定義された関数
    // add 関数を使用します。
    Stdout(add(10, 3)).newline;
    
    // std.stdioモジュールで、グローバルスコープに定義された関数
    // writefln 関数を使用します。
    // sample1401モジュールで、グローバルスコープに定義された関数
    // squareSum 関数を使用します。
    Stdout(squareSum(10,5)).newline;
    
    // ファイルスコープの関数にはアクセスすることができません。
    // 下の文のコメントアウトをはずすとコンパイルできなくなります。
    //Stdout(mul(4,7)).newline;
    
    return 0;
}
Page Top

実行結果 anchor.png

1
2
3
$ dmd sample1401.d -run sample1402p.d
13
100
Page Top

まとめ anchor.png

今回はimportで自分の作ったモジュールを読み込むことを行いました。
注意しなければならないのは、コンパイルの際、実行結果に示すように、dmdでは、importするモジュールも、importされるモジュールも両方とも同時にコンパイルしなければならないことです。
この講座の実行結果の例の部分では、毎回 -run を用いることで、実行ファイルを作成して即時実行するようにしていますが、
この場合は -run より前にmain関数のないファイルを、-run の直後にmain関数のあるファイルを指定します。
実行ファイルを生成したい場合は順番に関係なく、同時にまとめてコンパイルすればよいのです。

1
$ dmd sample1401.d sample1402p.d

dsssやbud等を使ってコンパイルする際には同時に指定する必要がないこともあります。

Page Top

#03 - パッケージ anchor.png

Page Top

今回は… anchor.png

モジュールをカテゴリごとに分類して、フォルダわけのような階層的な構造とするための、パッケージという仕組みを紹介します。

パッケージはファイルシステムでいうフォルダのような存在です。
今まで使用してきたものにその様子をうかがうことができます。

  • std.stdio
  • std.conv
  • std.string

など、これまでに扱ってきたもともとあるモジュールには、stdというものが名前の先頭についているかと思います。
これこそが、パッケージというものなのです。
たとえば、std.stdioは、stdパッケージに含まれる、stdioモジュールなのだということがわかります。
Tangoでは、 tango.io.Stdout など、より深い階層化が行われています。
tangoパッケージの中のioパッケージの中のStdoutモジュールということになりますね。

実際にパッケージを構成するときも、フォルダ構造を同じくします。
たとえば、

  • main.dファイル
  • testフォルダ
    • utilフォルダ
      • test.dファイル
    • func.dファイル

というようなフォルダ構成があったとしましょう。
ファイルは

  • main.d
  • test/util/test.d
  • test/func.d

の3つになりますね。
これらのそれぞれに対応したパッケージとモジュール名は、このようになります。

  • main
  • test.util.test
  • test.func

階層ごとに、.(ピリオド)で区切り、最後はファイル名から.dを抜いたものということになります。
ひとつ注意しなければならないことは、パッケージ名と同じモジュールは作成できないことです。
つまり、

  • main.d
  • test.d
  • test/func.d

というようなことはできません。
testモジュールと、testパッケージがかぶってしまうためです。
ほかにもパッケージ名やモジュール名はいくつかの制約を受けます。

Page Top

今回のミソ anchor.png

  • パッケージにより、モジュールを階層化することができる。
  • パッケージ名はフォルダ名と同じです
  • パッケージ名やモジュール名には、.(ピリオド)が使えない
  • パッケージ名やモジュール名には、予約語が使えない
  • パッケージ名やモジュール名には、基本的にすべて小文字*2
Page Top

サンプルコード anchor.png

main.d
Everything is expanded.Everything is shortened.
1
2
3
4
5
6
7
8
9
 
 
 
 
 
-
|
|
!
module main;
import sample.func;
import sample.util.test;
 
int main(char[][] args)
{
    test( func() );
    return 0;
}
sample/func.d
Everything is expanded.Everything is shortened.
1
2
3
4
5
6
7
8
 
 
-
!
-
-
!
!
module sample.func;
 
// グローバルスコープで関数を定義
int func()
{
    // 常に 0 を返す単純な関数です。
    return 0;
}
sample/util/test.d
Everything is expanded.Everything is shortened.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 
 
-
|
!
-
-
!
|
-
|
!
-
|
!
!
 
-
-
!
|
-
|
!
-
|
!
!
-
|
|
module sample.util.test;
 
// こうすることでTangoの時とPhobosの時で場合分けすることができます。
// 後のほうでより詳細に解説します。
version(Tango)
{
    // Tangoのときは tango.io.Stdout を読み込んで使う。
    import tango.io.Stdout;
    
    // グローバルスコープで test 関数を定義。
    // ここでは x が 0 であれば true と表示する関数とします
    void test(int x)
    {
        Stdout( x == 0 ).newline;
    }
}
else
{
    // Phobosのときは std.stdio を読み込んで使う。
    import std.stdio;
    
    // グローバルスコープで test 関数を定義。
    // ここでは x が 0 であれば true と表示する関数とします
    void test(int x)
    {
        writeln( x == 0 );
    }
}
// Phobosの時もTangoの時も両方でグローバルスコープに
// void test(int x)
// の関数が定義できました。

*1*3

Page Top

実行結果 anchor.png

1
2
$ dmd sample/func.d sample/util/test.d -run main.d
true
Page Top

まとめ anchor.png

もちろん今回も全てのファイルを同時にコンパイルします。

このように、階層化する理由として、カテゴライズがあります。
Tangoでは、

  • tango.io.*

のパッケージに属するものは、I/O*4関連のモジュールであることが、パッケージ名から一目瞭然です。

上手にパッケージやモジュールの名前を決めていきましょう。*5

Page Top

#04 - 名前空間 anchor.png

Page Top

今回は… anchor.png

名前空間についてお話します。
名前空間というのは、名前の衝突を避けるための仕組みです。
詳しくは[[Wikipedia:名前空間]]にうまく表記されているようなのでそちらを参考にしてもらうとして…
実際にどんな場面で役に立つのか?を中心にお話しします。

特に名前空間が役に立つ場面は、グローバルスコープに、同じ名前があったときのアクセスです。
たとえば、モジュールhogeにget関数があったとします。

1
2
3
4
5
module hoge;
char[] get()
{
    return "hoge";
}

また、モジュールfugaにもget関数があったとします。

1
2
3
4
5
module fuga;
char[] get()
{
    return "int";
}

このとき、これらhogeとhugaを同時にインポートした場合、どうしたらいいでしょうか?
get() として呼び出したらどうなるでしょうか?

1
2
3
4
5
6
7
8
9
import std.stdio;
import hoge;
import fuga;
 
int main(char[][] args)
{
    writeln( get() );
    return 0;
}

コンパイルすると、次のようなエラーメッセージが出てコンパイルできません。

1
2
$ dmd hoge.d fuga.d -run main.d
main.d(7): Error: hoge.get at hoge.d(2) conflicts with fuga.get at fuga.d(2)

「モジュールhogeのget関数と、モジュールfugaのget関数がかぶってるよ!どっち呼んだらいいかわかんないよ!」といわれているわけです。
ここで登場するのが、「名前空間」というわけですね。

D言語では、モジュールが自動でその中身をスコープとする名前空間を形成します。
つまり、モジュール名を指定すれば、名前空間にアクセスできるってことになりますね。

前述の例では、 hoge.get() や fuga.get() のようにすることで名前空間を使ったアクセスを行うことができるわけです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import std.stdio;
import hoge;
import fuga;
 
int main(char[][] args)
{
    // モジュールhogeのget関数を呼ぶ
    writefln( hoge.get() );
    // モジュールfugaのget関数を呼ぶ
    writefln( fuga.get() );
    return 0;
}
Page Top

今回のミソ anchor.png

  • グローバルスコープに同じ名前の関数や変数、型が存在する場合、直接使おうとするとコンパイルエラーが発生する
  • 名前の衝突を避けるためには名前空間を使う。
  • D言語ではモジュールが名前空間を作る
  • 名前空間の使い方は、以下のように.(ピリオド)を使う
    モジュール名.使いたい名前
Page Top

サンプルコード anchor.png

main.d
Everything is expanded.Everything is shortened.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
-
!
-
!
 
-
!
 
-
-
!
-
!
|
!
 
 
-
|
!
-
-
!
|
-
|
!
!
 
-
-
!
|
-
|
!
!
// hogeのインポート
import hoge;
// fugaのインポート
import fuga;
 
// そのままget関数を使おうとすると名前の衝突を起こす。
 
int main(char[][] args)
{
    // モジュールhogeのget関数を呼ぶ
    output( hoge.get() );
    // モジュールfugaのget関数を呼ぶ
    output( fuga.get() );
    return 0;
}
 
 
// 出力用の関数をPhobos/Tango共通のものを作る
// ちなみに、関数の定義は使うところよりも下のほうで行っても問題はない。
version(Tango)
{
    // Tangoのときはこっちを
    import tango.io.Stdout;
    void output(char[] text)
    {
        Stdout(text).newline;
    }
}
else
{
    // Phobosのときはこっちを
    import std.stdio;
    void output(char[] text)
    {
        writefln(text);
    }
}
hoge.d
Everything is expanded.Everything is shortened.
1
2
3
4
5
6
7
 
 
-
!
-
|
!
module hoge;
 
// モジュールhogeのget関数
char[] get()
{
    return "hoge".dup;
}
fuga.d
Everything is expanded.Everything is shortened.
1
2
3
4
5
6
7
 
 
-
!
-
|
!
module fuga;
 
// モジュールfugaのget関数
char[] get()
{
    return "fuga".dup;
}
Page Top

実行結果 anchor.png

1
2
3
$ dmd hoge.d fuga.d -run main.d
hoge
fuga
Page Top

まとめ anchor.png

グローバルスコープで名前が衝突してしまうのは、できれば避けたい事態ですね。
これを避けるために、ファイルスコープ*6があるわけです。
外部にさらす必要のない関数や変数、型*7はどんどんprivateにしたほうがいいでしょう。

また、モジュールがパッケージに属している場合は、パッケージ名から書いてくださいね。
例として、Tangoの整数を文字列に変換するtoString関数と、実数を文字列に変換するtoString関数がうまい具合にかぶっているので、これでパッケージからのアクセスの使い方を紹介します。

import tango.io.Stdout;
import tango.text.convert.Integer;
import tango.text.convert.Float;
int main(char[][] args)
{
    // tango.text.convert.Integerモジュールの、toString関数
    // 「整数」を文字列に変換します。
    Stdout( tango.text.convert.Integer.toString(12) ).newline;
    // tango.text.convert.Floatモジュールの、toString関数
    // 「実数」を文字列に変換します。
    Stdout( tango.text.convert.Float.toString(85) ).newline;
    return 0;
}
Page Top

#05 - インポートの応用 anchor.png

Page Top

今回は… anchor.png

前回にパッケージ名からすべてモジュール名を明記して呼び出すことで名前空間が使えることを示しましたが、これ、長くないでしょうか。
いちいち毎回記述するの、めんどうですよね。 そんなときに使えるのが、これ。
改名インポート。モジュール名を別の名前に変えて名前空間にアクセスすることができます。

import stdio = std.stdio;
// stdio.writefln() のように、
// 名前空間を stdio でアクセスできるようにした。

他にもインポートに関するものはいくつかあります。
staticインポート。必ずモジュール名を明記しなければ名前にアクセスできません。

static import std.stdio;
// std.stdio.writefln のように、
// モジュール名からアクセスしないと使えない。

選択インポート。使いたいものだけ選択してインポートすることができます。

import std.stdio: writefln, puts = writef;
// writeflnが普通に使えて、writefの代わりにputsが使える

改名選択インポート。改名と選択を組み合わせた感じ

import io = std.stdio : puts = writef;
// io.puts って感じに使える。

publicインポート。これを行ったモジュールをimportするだけで、publicの付いたモジュールが使えるようになる。まとめてインポートさせたいときなどに有効かも知れない。

public import std.stdio;
// io.puts って感じに使える。

詳しくは公式ページのほうに載っているので、参考にしていただきたい。

Page Top

今回のミソ anchor.png

  • staticインポート…必ず名前空間をつけなければアクセスできなくする
  • 改名インポート…名前空間の名前を変えてインポートする
  • 選択インポート…関数などの名前を変えてインポートする
  • 改名選択インポート…上二つの組み合わせ。
  • publicインポート…行ったモジュール自身をインポートすると同時にインポートされる
Page Top

#summary - まとめ anchor.png

Page Top

第14章のミソ anchor.png

  • モジュールを読み込むにはimportを使って次のようにします。
    import modulename;
  • importされた関数はそのファイルで使うことができる。
  • パッケージにより、モジュールを階層化することができる。
  • パッケージ名はフォルダ名と同じです
  • パッケージ名やモジュール名には、.(ピリオド)が使えない
  • パッケージ名やモジュール名には、予約語が使えない
  • パッケージ名やモジュール名には、基本的にすべて小文字*2
  • グローバルスコープに同じ名前の関数や変数、型が存在する場合、直接使おうとするとコンパイルエラーが発生する
  • 名前の衝突を避けるためには名前空間を使う。
  • D言語ではモジュールが名前空間を作る
  • 名前空間の使い方は、以下のように.(ピリオド)を使う
    モジュール名.使いたい名前
  • staticインポート…必ず名前空間をつけなければアクセスできなくする
  • 改名インポート…名前空間の名前を変えてインポートする
  • 選択インポート…関数などの名前を変えてインポートする
  • 改名選択インポート…上二つの組み合わせ。
  • publicインポート…行ったモジュール自身をインポートすると同時にインポートされる
Page Top

宿題 anchor.png

モジュールを分割してみましょう。

  • モジュール util.func に、二つの整数を足し算する int add(int a, int b) 関数を記述
  • モジュール util.textfunc に、二つの文字列を連結する char[] add(char[] a, char[] b) 関数を記述
  • 以下のコードの _____ を埋めて、コンパイルが通るようにしましょう
    import util._____;
    import _____ util.textfunc: _____;
    import stdio = _____: _____ = writefln;
    int main(char[][] args)
    {
        _____.output( util._____._____(10, 45) );
        _____.output( txtfn.add("hogehoge", "fugafuga") );
    }
    1
    2
    
    55
    hogehogefugafuga
Page Top

コメント anchor.png

今回の宿題の空欄が多いでしょうか?
ヒントは…

  • モジュール名
  • 改名インポート
  • 選択インポート
  • 改名選択インポート
  • 改名選択インポート
  • 改名選択インポートされた関数にアクセス
  • モジュール名
  • 関数名
  • 改名選択インポートされた関数にアクセス

となっております。それではお次にまいりましょう。
次回はいよいよ、オブジェクト指向です。

それではお次へどうぞ~


Page Top

投票とコメント anchor.png

Choices Vote
大変参考になった4  
参考になった0  
あまり参考にならなかった0  
まったく参考にならなかった0  

Show recent 10 comments. Go to the comment page.

  • Visitor/Sample1でWheelクラスthis(string aName)とaNameを受け取っている意味が無いような? -- ゲストEdit 2009-04-22 (水) 02:50:36
  • version指定に関しては、いまのところ、TangoでないのならPhobosとしているため、-version=Tangoとしていない場合はPhobos側が利用されます。そして、Tangoの場合はsc.iniなどにデフォルトで-version=Tangoとなっているため、結局のところコンパイルオプションでversion指定を行う必要はないのです。 -- SHOOEdit 2009-04-22 (水) 03:43:34
  • test( func() );抜けてたorzこれは恥ずかしい! 報告ありがとうございます! -- SHOOEdit 2009-04-22 (水) 03:44:05
  • Visitor/Sample1に関しては、ここで話すのもなんですが、阿呆なミスをやらかしてました。報告感謝です。ただ、あちらは誰でも書き換えることができるハズなので、気づいたら書き換えてしまって構いませんよ。 -- SHOOEdit 2009-04-22 (水) 03:46:54
  • version指定の説明ありがとうございます! -- ゲストEdit 2009-04-22 (水) 04:57:31
  • sample1402p.dのコンパイルが通りませんでした(多分D2.039orD2.040)。書式を使ってないのにwritef,writeflnを使うな、と、怒られましたorz write,writelnに直したら通りましたので報告させていただきます。 -- ゲストEdit 2010-02-06 (土) 10:58:07
  • 早速陳腐化の波が… すぐに直します -- SHOOEdit 2010-02-07 (日) 00:46:24
  • 迅速な対応ありがとうございました。お疲れ様です。 -- ゲストEdit 2010-02-07 (日) 09:56:19
  • import io = std.stdio : puts = writef; // io.puts って感じに使える。 とのことですが、io.puts できるのはstd.stdioでputsが定義されてるからのようで(import io = std.stdio だけでも)、宿題の3行目を std.stdio/output 7,8行目の頭をstdioとしたら stdio にoutputなんてねーよ禿げ、とコンパイラに怒られました。 -- ゲストEdit 2011-05-10 (火) 11:37:23
  • 途中送信してしまいました。(import io = std.stdio; だけでも、io.puts できる) ですね。 公式のモジュールの項を見ても、選択インポートで改名したシンボルは現在の名前空間に束縛されるらしいので、宿題の output のところは ____. は不要では? -- ゲストEdit 2011-05-10 (火) 11:44:43
Name:

*1 今回はTangoとPhobosの差はありません
*2 これは仕様ではなく、推奨されているものです。
*3 特に今回の物は、内部で分岐させることでPhobosの時とTangoの時両方でコンパイルできるようにしてあります。
*4 入出力
*5 ただし、Tangoの場合は本家で推奨しているような、すべて小文字のモジュール名は採用していないようです…
*6 privateな宣言や定義のこと
*7 構造体やクラスなど

Front page   Freeze Diff Backup Copy Rename ReloadPrint View   New Page Page list Search Recent changes   Help   RSS of recent changes (RSS 1.0) RSS of recent changes (RSS 2.0) RSS of recent changes (RSS Atom) Powered by xpWiki
Counter: 6318, today: 2, yesterday: 1
Princeps date: 2012-05-05 (Sat) 02:16:35
Last-modified: 2015-11-02 (Mon) 20:38:11 (JST) (1741d) by ゲスト
メインメニュー

ログイン

ユーザー名:


パスワード:





パスワード紛失  |新規登録

Menu