名前空間つきのモジュールを使う

いつからこうなっているか知らないんですけど, Erlangって名前空間があるらしいです.

-module(ffhh.followers.director).

こんな感じでモジュールを任意の名前空間に属させるコトができます. アンダースコアで長ったらしい名前をつけなくてもいいワケですね! 例えば呼び出しとかも, 同一の名前空間に属していれば同一部分は省略できます. こんな感じ.

-module(ns.mod1).

-export([fun1/0]).

fun1() ->
    Result = mod2:fun1(),   % 実際にはns.mod2:fun1/0がコールされている
    .lists:reverse(Result). % ただ存在しないモジュールはルートから検索してくれるワケではないので,
                            % 先頭にドットをつけて, 明示的にルートから検索するようにしないとダメ
                            % (ちょっとめんどくさい)

ステキ! 少しめんどうなところもあるし, 実際OTP使ったりすると, どこから呼ばれるのかわからないので, 先頭にドットつけてフルパスで指定した方が結局無難じゃん! とかありますけども, まぁそれは些細な話というコトで.

んで名前空間を使うと, ディレクトリの構造も変えなくちゃいけなくて, CPANよろしくな構造にしないといけないんですよね.

コレを:

src
├── ns.erl
└── ns_mod1.erl

こんな風に:

src
├── ns.erl
├── ns
     └── mod1.erl(モジュール名はns.mod1)

あと*.beamファイルの出力先も同じような構造になってなくちゃいけません. *.erlと同じところに入れるなら簡単ですが, ebinなど別のディレクトリに出力するならディレクトリを作りつつコンパイルしなくちゃいけないはず.

というコトでそれを行うRakefileを書いてみた*1. MakefileじゃないのはただボクがMakefileの書き方をよく知らないってだけです.

require 'rake/clean'

BEAM_DIR = 'ebin'

def dirname(files)
  (files.map {|f| File.dirname f}.uniq - [BEAM_DIR]).sort_by {|f| f.length}
end

BEAM_OBJ = FileList['src/**/*.erl'].sub(/^src/, BEAM_DIR).ext 'beam'
BEAM_DIRS = dirname BEAM_OBJ

CLOBBER.include BEAM_OBJ + BEAM_DIRS

BEAM_DIRS.each {|dir| directory dir}

def modules(*mods)
  files = mods.flatten.map do |mod|
    segs = mod.split '.'
    segs[-1] = "#{segs.last}.beam"
    File.join BEAM_DIR, segs
  end

  dirname(files) + files
end

def resolve(ext)
  lambda {|obj| obj.sub(/^#{BEAM_DIR}/, 'src').sub(/\.[^.]+$/, ".#{ext}")}
end

ERLC = 'erlc'
ERLC_FLAGS = '-W -Iinclude'

task :default => modules('ns', 'ns.mod1')

rule /\.beam$/ => resolve('erl') do |t|
  sh "#{ERLC} #{ERLC_FLAGS} -o #{File.dirname t.to_s} #{t.source}"
end

楽ちんだぜー!

*1:ebinは最初からある想定です. *.appを置かない場合はebinも作るようにした方がいいと思います