本稿は「function_exported?/3 する前には Code.ensure_loaded/1 を呼び出そう」をもとに加筆・補正し、文章を整えました。
function_exported?/3でビヘイビアの関数の実装を確かめる
ビヘイビアが実装されたモジュールから、それらの関数を呼び出すときは、予めfunction_exported?/3
によりその関数が備わっているかどうか確かめます。たとえば、つぎのようなFoo
ビヘイビアを実装したFooImpl
モジュールがあるとします。
defmodule Foo do
@callback foo() :: :ok
end
defmodule FooImpl do
@behaviour Foo
@impl Foo
def foo() do
:ok
end
end
呼び出す関数からは、つぎのようにfunction_exported?/3
で引数のモジュールの関数がエクスポートされているか、つまりFoo
ビヘイビアが実装されているかを確認してから呼び出します。これでまったく問題のないコードにみえるかもしれません。
defmodule Bar do
def call_foo(mod) do
if not function_exported?(mod, :foo, 0) do
raise "Foo behaviour is not implemented"
end
mod.foo()
end
end
call_foo(FooImpl)
しかし、このコードは、mix
から起動したときに動作しない場合があります。
なぜ動作しないのか
ドキュメントのfunction_exported?/3
の項には、つぎのような注意書きがあります。引数のモジュールが予めロードされていない場合、function_exported?/3
はモジュールの読み込みはしません。つまり、関数が実装されていない場合だけではなく、モジュールがまだロードされていないときもfalse
が返されるのです。
Note that this function does not load the module in case it is not loaded.
モジュールを用いるには、そのモジュールが事前にErlang VMにロードされていなければなりません。それを理解するには、Erlangがモジュールをロードする戦略を知る必要があります。戦略はつぎのふたつです(Erlang VMのデフォルトは後者)。
- 組み込みモード アプリケーションを起動したときに、すべてのモジュールがロードされます。
- 対話モード 最初にそのモジュールの関数を呼び出したときに、そのモジュールがロードされます。
mix
からアプリケーションを動かした場合には 対話モードになります。そのため、前掲のコードを動かしたとき、FooImpl
モジュールの関数をまだどれも呼んでいなければ、function_exported?/3
はfalse
を返してエラーになるのです。他方で、先に実行されたコードがFooImpl
モジュールの関数を呼び出していると、function_exported?/3
の戻り値はtrue
となり、正しくmod.foo()
が呼ばれます。
つまり、mix
から起動したときは、どのコードをとおったかによって動作は変わってしまいます。これが「動作しない場合」があると述べた理由です。なお、Distilleryで生成すると、デフォルトで組み込みモードになります。
予めモジュールのロードを確かめる
ドキュメントのfunction_exported?/3
の項は、Code.ensure_loaded/1
の参照を促します。引数のモジュールがロードされているかどうか確かめて、結果がまだであればロードしてくれる関数です。つぎのようにCode.ensure_loaded/1
の呼び出しを書き加えると、ビヘイビアのモジュールの読み込みが実行されます。
defmodule Bar do
def call_foo(mod) do
Code.ensure_loaded(mod)
if not function_exported?(mod, :foo, 0) do
raise "Foo behaviour is not implemented"
end
mod.foo()
end
end
call_foo(FooImpl)
もっとも、モジュールの読み込みは、何らかの理由で失敗するかもしれません。そのときの戻り値は{:error, reason}
になりますので、エラーの分岐を書いた方がより厳密です(成功であれば{:module, module}
)。もっとも、その場合にはつぎのfunction_exported?/3
がfalse
を返して、結局ビヘイビアを実装していないというエラーになります。この例ような簡単なコードでしたら、これで済むこともあるでしょう。
Top comments (0)