LoginSignup
10
9

More than 1 year has passed since last update.

Juliaで自作のモジュールや関数をファイル単位で分割する方法

Last updated at Posted at 2021-08-03

本記事によってできるようになること

  • 【課題1】同じファイル内、またはノートブック内で定義したモジュール内の関数をmodule.func()と呼び出すのではなくfunc()と呼び出せるようになる。
  • 【課題2】自作モジュールまたは関数を別ファイルから読み込むことができるようになる。

以上2点を目標に解説をしたいと思います。

話が少し長くなると思うので、結論だけ知りたい人はまとめを見てください。

【課題1】

まずはモジュールを定義します。簡単な四則計算ができるモジュールを定義しました。

Juliaでは関数の書き方がいろいろあります。addのように一行でかけたり、submultのように複数行に分けてかけたりもします。基本的にはmultのような書き方が一般的だと思います。

Calc.jl
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

さてこのモジュールを使用するとき通常ならこうします。

case1.jl
Calc.add(2, 4)
Calc.sub(6, 4)
Calc.mult(10, 4)

ですが、いちいちモジュール名を書くのもうっとおしくなってくることがあるわけです。つまり、Calc.addaddと使いたいし、Calc.subsubと使いたいのです。

うまくいかなかった方法

そこでusingを使ってCalcモジュールを読み込んでみます。

using_calc.jl
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の仕組みを簡単にご紹介します。指定したモジュールはusingimportを使って呼び出しますがこのとき、LOAD_PATH という環境変数に収められたパスにから検索されます。

load_path.jl
LOAD_PATH
>> 3-element Vector{String}:
 "@"
 "@v#.#"
 "@stdlib"

つまり先ほどのエラーはこの__LOAD_PATH__上にCalcが存在しないよ、というエラーなのです。

従ってモジュールを定義しているファイルのパスをpush!等を使ってここに追加すればいいわけです。

push_load_path.jl
push!(LOAD_PATH, "/foo/hoge")
>> 4-element Vector{String}:
 "@"
 "@v#.#"
 "@stdlib"
 "/foo/hoge"

ではusingしてみましょう。

using_calc2.jl
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_calc3.jl
using .Calc
# または
using Main.Calc

これで所望の結果を得ることができます。自作モジュールはMainという名前空間の中に定義されることになっています。これは自作モジュール内に、また新たなモジュールを定義することと等しいと考えればとても自然なことなのではないでしょうか。

submodule.jl
# 暗黙的にMainという名前空間内に定義される
module Main

    module Calc
        add(x, y) = x + y
    end

using .Calc
end

では、addしてみましょう。

use_method.jl
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モジュールを次のように書き直せばきちんと動くようになります。

Calc_modified.jl
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していなくてもモジュール名なしに関数を呼び出すことができます。

without_export.jl
using Calc: mult

using モジュール名: 関数 で指定の関数のみを呼び出すことができます。こうして指定した場合、指定した関数以外は取り込まないので、モジュール名なしにaddsubは使えません。

importの場合も同じです。ただimportusingと動きが異なっていてimport モジュール名: 関数名とせずにimport モジュール名とするとき、exportに関係なく、モジュール名.関数名としか関数を呼べないです。詳しくはこちらの記事がわかりやすかったです。

脱線

import モジュール名: 関数名で呼び出した関数は、自分の好きなように定義しなおすことができます(多重ディスパッチ)。

multiple_dispatch.jl
import Base: +

+() = println("足し算するよ")

# モジュール名.関数名でもよい
Base.+() = println("足し算するよ")

#【課題2】

では次の課題に行きます。これはとても簡単で分割したファイルを呼び出すにはinclude("ファイルパス")を使うだけです。相対パスでも絶対パスでもどちらでも大丈夫です。但し、モジュールをほかの人に公開するときに絶対パスだと環境によっては動かないので、書籍では相対パスを推奨していました。

include.jl
include("./Calc.jl")

まとめ

sammary.jl
# 【課題1】
# 但し、モジュール内で関数exportしておく。
using .モジュール名
# または
using Main.モジュール名

# 【課題2】
include("ファイルの相対パス")


参考文献

1から始めるJuliaプログラミング

10
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
9