本記事によってできるようになること
- 【課題1】同じファイル内、またはノートブック内で定義したモジュール内の関数を
module.func()
と呼び出すのではなくfunc()
と呼び出せるようになる。 - 【課題2】自作モジュールまたは関数を別ファイルから読み込むことができるようになる。
以上2点を目標に解説をしたいと思います。
話が少し長くなると思うので、結論だけ知りたい人はまとめを見てください。
【課題1】
まずはモジュールを定義します。簡単な四則計算ができるモジュールを定義しました。
Juliaでは関数の書き方がいろいろあります。add
のように一行でかけたり、sub
やmult
のように複数行に分けてかけたりもします。基本的にはmult
のような書き方が一般的だと思います。
module Calc
add(x, y) = x + y
sub(x, y) = begin
res = x - y
println("$x-$y=", res)
end
function mult(x, y)
x*y
end
end
さてこのモジュールを使用するとき通常ならこうします。
Calc.add(2, 4)
Calc.sub(6, 4)
Calc.mult(10, 4)
ですが、いちいちモジュール名を書くのもうっとおしくなってくることがあるわけです。つまり、Calc.add
はadd
と使いたいし、Calc.sub
はsub
と使いたいのです。
うまくいかなかった方法
そこでusing
を使ってCalcモジュールを読み込んでみます。
using Calc
>> ArgumentError: Package Calc not found in current path:
- Run `import Pkg; Pkg.add("Calc")` to install the Calc package.
Stacktrace:
[1] require(into::Module, mod::Symbol)
@ Base .\loading.jl:871
[2] eval
@ .\boot.jl:360 [inlined]
[3] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base .\loading.jl:1094
するとパッケージCalcが探せないよというエラーが出ます。
ここでJuliaのusingの仕組みを簡単にご紹介します。指定したモジュールはusing
やimport
を使って呼び出しますがこのとき、LOAD_PATH という環境変数に収められたパスにから検索されます。
LOAD_PATH
>> 3-element Vector{String}:
"@"
"@v#.#"
"@stdlib"
つまり先ほどのエラーはこの__LOAD_PATH__上にCalcが存在しないよ、というエラーなのです。
従ってモジュールを定義しているファイルのパスをpush!
等を使ってここに追加すればいいわけです。
push!(LOAD_PATH, "/foo/hoge")
>> 4-element Vector{String}:
"@"
"@v#.#"
"@stdlib"
"/foo/hoge"
ではusing
してみましょう。
using Calc
>>
┌ Info: Precompiling Calc [top-level]
└ @ Base loading.jl:1317
WARNING: --output requested, but no modules defined during run
ArgumentError: Invalid header in cache file C:\Users\USER_NAME\.julia\compiled\v1.6\jl_5A1E.tmp.
Stacktrace:
[1] preferences_hash(cachefile::String)
@ Base .\loading.jl:1478
[2] compilecache(pkg::Base.PkgId, path::String, internal_stderr::IJulia.IJuliaStdio{Base.PipeEndpoint}, internal_stdout::IJulia.IJuliaStdio{Base.PipeEndpoint})
@ Base .\loading.jl:1337
[3] compilecache(pkg::Base.PkgId, path::String)
@ Base .\loading.jl:1306
[4] _require(pkg::Base.PkgId)
@ Base .\loading.jl:1021
[5] require(uuidkey::Base.PkgId)
@ Base .\loading.jl:914
[6] require(into::Module, mod::Symbol)
@ Base .\loading.jl:901
[7] eval
@ .\boot.jl:360 [inlined]
[8] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base .\loading.jl:1094
じゃあこれでusing
できるのかというとこれではできないのです。というか詳しくはわかんないのですけどこれではできません。ちゃんとしたパッケージの体裁をとってないのでうまくJuliaが読み込んでくれていないのかな?といったところ。
エラーの解決策としては、自作のパッケージを作成しそのパスを__LOAD_PATH__に追加することでusing
できるようになります。が、パッケージの製作に関してはまた今度まとめます。難しくないのですが今回はもっと簡単な方法がありますので、そちらをご紹介します。
うまくいった方法
結論としてはusing
の方法が違います。こちらをご覧ください。
using .Calc
# または
using Main.Calc
これで所望の結果を得ることができます。自作モジュールはMainという名前空間の中に定義されることになっています。これは自作モジュール内に、また新たなモジュールを定義することと等しいと考えればとても自然なことなのではないでしょうか。
# 暗黙的にMainという名前空間内に定義される
module Main
module Calc
add(x, y) = x + y
end
using .Calc
end
では、add
してみましょう。
add(2, 3)
>>UndefVarError: add not defined
Stacktrace:
[1] top-level scope
@ In[4]:1
[2] eval
@ .\boot.jl:360 [inlined]
[3] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base .\loading.jl:1094
はい。エラーですね(笑)実はもう一つ重要なことがあって、usingで取り出せる関数はexport
で指定されている関数だけなのです。つまりCalcモジュールを次のように書き直せばきちんと動くようになります。
module Calc
# 追加
export add, sub
add(x, y) = x + y
sub(x, y) = begin
res = x - y
println("$x-$y=", res)
end
function mult(x, y)
x*y
end
end
長かったですがこれでようやくadd
のみで動くことになります。但し、export
に指定されていないmult
はモジュール名.
でしか呼べないことに注意しましょう。
using importのおまけ
先ほど、using
するときexport
なしの関数はモジュール名なしには呼べないと説明しました。ですが、例外的に次のように関数を呼ぶとexport
していなくてもモジュール名なしに関数を呼び出すことができます。
using Calc: mult
using モジュール名: 関数
で指定の関数のみを呼び出すことができます。こうして指定した場合、指定した関数以外は取り込まないので、モジュール名なしにadd
やsub
は使えません。
import
の場合も同じです。ただimport
はusing
と動きが異なっていてimport モジュール名: 関数名
とせずにimport モジュール名
とするとき、export
に関係なく、モジュール名.関数名
としか関数を呼べないです。詳しくはこちらの記事がわかりやすかったです。
脱線
import モジュール名: 関数名
で呼び出した関数は、自分の好きなように定義しなおすことができます(多重ディスパッチ)。
import Base: +
+() = println("足し算するよ")
# モジュール名.関数名でもよい
Base.+() = println("足し算するよ")
#【課題2】
では次の課題に行きます。これはとても簡単で分割したファイルを呼び出すにはinclude("ファイルパス")
を使うだけです。相対パスでも絶対パスでもどちらでも大丈夫です。但し、モジュールをほかの人に公開するときに絶対パスだと環境によっては動かないので、書籍では相対パスを推奨していました。
include("./Calc.jl")
まとめ
# 【課題1】
# 但し、モジュール内で関数exportしておく。
using .モジュール名
# または
using Main.モジュール名
# 【課題2】
include("ファイルの相対パス")
参考文献