このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

Erlang実験室:-includeの使い方

実験つうより実践の問題かも知れませんが、見出しは実験室。以下で述べることは、確定的ではなくてまだ暫定案です。

内容:

  1. プリプロセッサと-includeディレクティブ
  2. モジュール、アプリケーション、プロジェクト
  3. ヘッダファイルの分類とインクルードの書き方
  4. 同一プロジェクトの他のアプリケーションからのインクルード
  5. 三者のヘッダーファイル
  6. 依存性の問題とか

プリプロセッサと-includeディレクティブ

Erlangには、CのCPPと似たプリプロセッサeppが付いています。eppは、コンパイル前にソースコードのテキスト処理をします*1。'P'(大文字)オプションを付けてコンパイルすると、プリプロセスの結果を拡張子.Pのファイルに書き出します。


1> compile:file("foo.erl", ['P']).
{ok,[]}
2>

コマンドラインコンパイラerlcでは-Pオプションです。

今日の話題は、Cの#includeに相当する-includeディレクティブです(Erlangでは属性と呼ぶのですが、ピンと来ないのでディレクティブにします)。-include(ファイル名の文字列). とすると、指定されたファイル(ヘッダーファイル、インクルードファイル)をソースに取り込みます。ファイル名が相対パスのときは、そのソースファイルが置かれているディレクトリを基点として相対パスを解釈するのが基本です。ただし以下では、カレントディレクトリとソースディレクトリが一致していることを仮定します*2

多くのコンパイラがそうであるように、ヘッダーファイルを検索するディレクトリを指定できます。EShellからはi(小文字)オプション、コマンドラインからは-Iオプションを使います。


-include("foo.hrl").
と書いてあって、オプション [{i, "../include"}, {i, "/home/hiyama/erlang/include"}] が指定されていれば、./foo.hrl、../include/foo.hrl、/home/hiyama/erlang/include/foo.hrl の順でヘッダファイルを探します。

●モジュール、アプリケーション、プロジェクト

Erlang開発時の、ファイルやディレクトリの編成を説明しておきます。

Erlangで「アプリケーション」と言ったら単にモジュールの集まりです*3。物理的には、ディレクトリ$APP/(アプリケーション・ディレクトリと呼ぶ)の下に $APP/src/*.erl として置かれたソースファイル群(と若干の関連ファイル群)がアプリケーションの実体です。

複数のアプリケーションをまとめた塊をここではプロジェクトと呼ぶことにして、対応するディレクトリは$PROJ/と表記します。プロジェクトが複数のアプリケーション foo, bar, .. などから構成されるなら、$PROJ/foo/, $PROJ/bar/, ... などが配下のアプリケーションディレクトリとなります。

●ヘッダファイルの分類とインクルードの書き方

ヘッダファイルを用途や出所により次のように分類しましょう。

  1. 公開しない内部的なヘッダファイル
  2. 公開するヘッダファイル(公開API利用者に必要な定義など)
  3. Erlang/OTPに付属の標準的なヘッダファイル
  4. 同一プロジェクト内の他のアプリケーションが公開しているヘッダファイル
  5. 三者によるアプリケーションのヘッダファイル

最初の3種については、次のようにインクルードすればいいでしょう。


-include("./foo_internal.hrl"). % 内部的なヘッダファイル
-include("../include/foo_api.hrl"). % 公開するヘッダファイル
-include_lib("kernel/include/file.hrl"). % OPT標準のヘッダファイル
[修正]"/kernel/include/file.hrl" となってました。"kernel/include/file.hrl"です。[/修正]

内部的なヘッダファイルは、$PROJ/foo/src/に置き、公開するヘッダファイルは$PROJ/foo/include/に置くことに約束して、iオプションに頼らずに相対パスを書いたほうが事情がハッキリと伝わります。"./foo_internal.hrl" の "./" は無意味のようですが、これは後で説明します。

三番目の-include_libは便利な機能で、次のメリットがあります。

  1. 物理パスの違いを吸収してくれる。
  2. 最新バージョン(highest version)を選んでくれる。

例えば僕の場合、Windowsマシンにおける "file.hrl" のパスは "c:/Installed/erl5.6.4/lib/kernel-2.12.4/include/file.hrl"、Linuxマシンでは "/usr/local/lib/erlang/lib/kernel-2.12.5/include/file.hrl" ですが、-include_libを使えば、Erlang/OTPのインストール場所やバージョンが違っていても、また複数のバージョンが同一の場所にインストールされていても、適切なヘッダファイルを選んでくれます*4

いずれにしても、絶対パスを書いてはいけませんLinuxの /usr/local/lib/erlang/ は標準的だから、とか安易な想定はダメです。/usr/local/ はconfigureに与えるパラメータなので、都合で、/usr/lib/erlang/ や /home/hiyama/Erlang/lib/erlang/ になったりします。

●同一プロジェクトの他のアプリケーションからのインクルード

アプリケーション$PROJ/fooが、別なアプリケーション$PROJ/barの機能を使うとします。すると、例えば $PROJ/bar/include/bar_api.hrl のインクルードが必要になります。やり方はいくつかあります。

  1. $PROJ/bar/include/bar_api.hrl の絶対パスを書く。
  2. 相対パス ../../bar/incude/bar_api.hrl を書く。
  3. iオプションに$PROJ/bar/includeを含めて、-include("bar_api.hr."). と書く。
  4. ERL_LIBS環境変数を使って、-include_lib("bar/include/bar_api.hrl"). と書く。

絶対パスダメに決まっています。その他は優劣が付けがたい感じ。一時僕は、ERL_LIBS環境変数に$PROJを含めて -include_libを使う方法を気に入ってました。が、これだと、ヘッダファイルがOTP標準かそうでないかの区別が付きにくいのです。

同じプロジェクト内の他のヘッダファイルをインクルードするには、ディレクトリ構造を固定して、相対パスを使ったほうがいいと今は思っています。

  • ../include/*.hrl -- 自分の公開ヘッダ
  • ../../$OTHER_APP/include/*.hrl -- 他のアプリケーションの公開ヘッダ

プロジェクトがある程度の規模になると、目的のヘッダをどのアプリケーションが公開しているか不明になったり、ヘッダの場所が移動したりします。多くのアプリケーションで共通に使うヘッダは、$PROJ/include/ とか $PROJ/common/include/ とかに入れるようにするといいと思います。

●第三者のヘッダーファイル

自プロジェクトで第三者のアプリケーションを利用すれば、そのヘッダファイルをインクルードすることになります。

1つの方法は、プロジェクトのディレクトリツリーのなかに第三者アプリケーションを埋め込んでしまうことです。例えば、bazが第三者のアプリケーションだとして、次のようにインクルードします。


-include("../../baz/include/baz_def.hrl").
見た目は、自プロジェクト内のアプリケーションと変わりません。bazが自プロジェクトと緊密に関連するならこの方法でもいいでしょう。

「他人のものだから」と気になるなら、


-include("../../../vendor/baz/include/baz_def.hrl").
とか。".." が3つもあるのがナンだけど。

三者のアプリケーションを$PROJ内に取り込まないなら、コンパイラのiオプションで場所を指定することになるでしょう。インクルードパスは、環境変数、makeの変数、シェルの変数などで指定します。インクルードパスに baz/include/ の位置を入れておけば、ソース上は次のように書けばOKです。


-include("baz_def.hrl").

三者のヘッダファイルと、自アプリケーションの内部ヘッダファイルがすぐに区別できないのがイヤなので、次の約束はどうでしょう。

  • 内部ヘッダファイルは、"./" から始める("." はソースディレクトリ)
  • 三者のヘッダファイルは、"./" を書かない(「どこかにある」という雰囲気)。

●依存性の問題とか

Cの場合と同じく、ソースとヘッダの数が増えると、依存性が問題になってきます。ヘッダファイルが変更されていたのに再コンパイルを忘れて悲惨なことになるとか。gccなどは、ソースとヘッダの依存性を調査してMakefileを出力してくれます(-Mオプション)が、Erlangで同じ機能は見つかりませんでした(たぶん、どっかにあるとは思うんですが)。

上で述べたような約束を守っておけば、次のような方針で依存性をチェックできます。

  1. -include_libを使っているのは標準ヘッダだから、これは毎回調べる必要はない。
  2. ./、または../から始める相対パスは、ソースディレクトリを基点にアクセスすればよい。
  3. それ以外のファイルは、インクルードパス(ディレクトリの並び)を順に検索する。

人間が目で見たときも、当該ソースがどんな種類のヘッダファイルに依存しているかが一目でわかるのはいいことです。

*1:eppは、単なるテキストプロセッサではなくて、Erlangの文法を理解して動くので、CPPのように他の目的に使うのは難しいでしょう。

*2:ソースディレクトリ以外からコンパイルをするとややこしいことになる可能性があるので、やめたほうがいいです。

*3:OTP設計原理に従って編成されたモジュール集合を「アプリケーション」と呼ぶときもあります。

*4:最新バージョンの選択はコードサーバーも行うようです。