Juliaのモジュール周り

## Juliaのモジュール

いつも自分でよくわからなくなるので、[マニュアル](https://docs.julialang.org/en/v1/manual/modules/)をまとめてみる。

モジュールは、Juliaのコードの単位で、階層化可能な名前空間である。

``` julia
module モジュール名
中身
end
```

- 一般にモジュールの中身はインデントしない。全部インデントすることになってしまうから。

- モジュール名とファイル名には一般に対応関係は無い。1つのファイルに複数のモジュールを
書くこともあるし、下のようにして1つのモジュールを複数に分けることもある。

``` julia
module SomeModule
include("file1.jl")
include("file2.jl")
end
```

- モジュール名は型の名前と同じで、`UpperCamelCase`のように、大文字で始めるキャメルケースで書き、
型の名前との競合を防ぐため、可能なら複数形にする。

``` julia
module FastThings
struct FastThing
    ...
end
end
```

## 修飾名

モジュールの名前空間はネストし階層化することができる。

``` julia
module A
  y = 0
  module B
    x = 0
  end
end
```

ある名前の親モジュールを参照するには`parentmodule`関数を用いる。

``` julia
julia> parentmodule(UnitRange)
Base
```

- モジュールの外から名前にアクセスするには、`Base.UnitRange`のようにモジュール名を前につなげる。デリミタは`.`。
- シンボルのみの名前の場合はパーザの都合で`:`も必要。`Base.:+`のように書く。また`==`などの一部の演算子は`Base.:(==)`のようにカッコも必要。

## export
モジュールが公開する名前は`export`リストに書く。exportは通常モジュールの冒頭に書くがそうしなければならないわけではない。また、このリストを複数書くこともできる。ここにモジュールの提供する外部インターフェイスを記述することが期待されている。

ただし、エクスポートされていない名前でも修飾名を使えばいつでもアクセスできるので、モジュールに隠蔽の機能はない。このあたり実にJuliaっぽい。`export`リストにある名前は、後述の`using`を用いた際に名前空間に取り込まれるというだけで、`import`だけ使っている場合にはあまり意味がない。

```julia
module A
export x, y
x = 0
y = 1
z = 2
end
```

このように書くと、モジュール`A`の`x`と`y`は公開されているがzは公開されていない状態になる。

``` julia
julia> using .A
julia> x
0
julia> y
1
julia> z
ERROR: UndefVarError: z not defined
```
`x`と`y`はアクセスできるようになったが、`z`は出来ていない。ただし修飾名を使えば`z`にもアクセスできる。

``` julia
julia> A.z
2
```
ここで`using .A`は、同じレベルにあるモジュール`A`を利用するという宣言。`using A`で良さそうな気がするがだめ。モジュール`A`は、implicitなモジュール`Main`のサブモジュール`Main.A`として定義されている。`using`でなにもつけないと絶対パス指定になるので、`A`だけでは指定できないということらしい。`using Main.A`ならOK。

## モジュールとパッケージ

Juliaにはモジュールと独立にパッケージがある。パッケージはひとかたまりになったライブラリで、特定の構造を持つディレクトリに存在し、トップレベルにパッケージと同名のモジュールを持たなければならない。また、`Project.toml`というファイルがなければならない。

パッケージを作るには、Julia REPLのパッケージモードの`create`コマンドを用いると簡単。REPLから`]`をタイプするとパッケージモードになる。

``` julia
pkg> generate HelloWorld
```
こうすると、`HelloWorld`というディレクトリの中に`Project.toml`と`src/HelloWorld.jl`が作られる。REPLから`;`をタイプするとシェルモードに入るので、そこで確認してみよう。`.jl`ファイルにはテンプレートがすでにできている。

``` julia
shell> tree HelloWorld
HelloWorld
├── Project.toml
└── src
    └── HelloWorld.jl

1 directory, 2 files

shell> cat HelloWorld/src/HelloWorld.jl
module HelloWorld

greet() = print("Hello World!")

end # module

shell> 
```
ここでは、ディレクトリ名がパッケージ名と同じになっているが、これは必須ではない。任意のディレクトリ名が使用できる。

これでパッケージが一応できたわけだが、これだけでは使用できない。

``` julia
julia> import HelloWorld
ERROR: ArgumentError: Package HelloWorld not found in current path:
```

Juliaにこのパスにこのようなライブラリがあることを知らせる必要がある。それには、パッケージモードで`activate`コマンドを用いる。

``` julia
(@v1.7) pkg> activate HelloWorld
  Activating project at `~/juliatest/HelloWorld`
```
これでimportできるようになった。

``` julia
julia> import HelloWorld
[ Info: Precompiling HelloWorld [9c493a92-d783-4ef9-b670-e336ee782ab6]

julia> HelloWorld.greet()
Hello World!
```

## importとusing

Juliaにはモジュールやパッケージを使用可能にする方法として、`import`と`using`の2つが用意されている。

`import`はパッケージを読み込むために用いる。同じソースコード上のモジュールはすでに読み込まれているので、改めて`import`することにはあまり意味はない。

`using`は`import`の機能に加えて、そのモジュールで`export`されている名前を、現在の名前空間に読み込む機能を持つ。

``` julia
julia> module A
       export x
       x = 1
       end
Main.A

julia> x
ERROR: UndefVarError: x not defined

julia> import .A

julia> x
ERROR: UndefVarError: x not defined

julia> using .A

julia> x
1
```

## 名前を指定した読み込み
明示的に読み込む名前を指定することもできる。exportされていなくても読み込める。これはimportでもusingでも同じようにできる。

``` julia
julia> module A
       x = 1
       end
Main.A

julia> import .A: x

julia> x
1
```

この書き方だと、なぜかモジュール名自体は名前空間に取り込まれない。

``` julia
julia> module A
       module B
       x = 1
       end
       end
Main.A

julia> using .A.B: x

julia> B
ERROR: UndefVarError: B not defined
```
こういう場合は `using .A.B: x, B`のように書けばいいらしい。

import でも usingでも全く効果が同じなわけではなく、importでないと駄目なケースがある。モジュール内の関数にメソッドを追加するには、importでないといけない。

``` julia
julia> module A
       function x(i::Int64) x end
       end
Main.A

julia> import .A: x

julia> function x(i::Float64) x end
x (generic function with 2 methods)
```

これをusingでやろうとすると、こうなる。

``` julia
julia> using .A: x

julia> function x(i::Float64) x end
ERROR: error in method definition: function A.x must be explicitly imported to be extended
Stacktrace:
 [1] top-level scope
   @ none:0
 [2] top-level scope
   @ REPL[3]:1
```

不用意に定義を追加できないようにという趣旨なのだろう。でも、`x`は見えているのにメソッドが追加できないということなので、どうも見え方にも種類があるらしい。ややこしい話だ。そもそも、定義を追加するときには明示的にパッケージを指定するようにしたほうが良さそう。

``` julia
julia> using .A

julia> function A:x(i::Float64) x end
```

## as によるリネーム
名前空間に読み込むときにすでに同名の名前があると衝突して読み込めない。その場合には`as`を用いてリネームする。

``` julia
julia> module A
       x = 0
       end
Main.A

julia> x = 1
1

julia> import .A: x
WARNING: import of A.x into Main conflicts with an existing identifier; ignored.

julia> import .A: x as x0

julia> x, x0
(1, 0)
```
モジュールをまるごとリネームすることもできる。

```
julia> import .A as X

julia> X.x
0
```