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

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

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

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

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

# 【課題1】

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

Juliaでは関数の書き方がいろいろあります。`add`のように一行でかけたり、`sub`や`mult`のように複数行に分けてかけたりもします。基本的には`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.add`は`add`と使いたいし、`Calc.sub`は`sub`と使いたいのです。

## うまくいかなかった方法

そこで`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の仕組みを簡単にご紹介します。指定したモジュールは`using`や`import`を使って呼び出しますがこのとき、__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 モジュール名: 関数` で指定の関数のみを呼び出すことができます。こうして指定した場合、指定した関数以外は取り込まないので、モジュール名なしに`add`や`sub`は使えません。

`import`の場合も同じです。ただ`import`は`using`と動きが異なっていて`import モジュール名: 関数名`とせずに`import モジュール名`とするとき、`export`に関係なく、`モジュール名.関数名`としか関数を呼べないです。詳しくはこちらの[記事](https://qiita.com/cometscome_phys/items/5c98aef4c10a8a575f81)がわかりやすかったです。

## 脱線
`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プログラミング](https://www.coronasha.co.jp/np/isbn/9784339029055/)

https://www.it-swarm-ja.com/ja/julia/julia%E3%81%AB%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/824402325/

https://qiita.com/cometscome_phys/items/5c98aef4c10a8a575f81