Julia言語 でコード書いて人生を (N+M) ≥ 1 歩進める(2022 年 12 月 版)

# 本日は

Julia アドベントカレンダーとして投稿です．

https://qiita.com/advent-calendar/2022/julia

[Julia言語 を導入し人生を N ≥ 1 歩進める(2022 年 12 月 版)](https://qiita.com/SatoshiTerasaki/items/3c7fcee3a5716b763372) の続きとして書きます. したがって `N ≥ 1` 歩人生を進めていることを仮定します． つまり Julia を導入して REPL で遊べるようになったとします. 

さて `N` 歩進んで次に進むべき場所がわからない． だって REPL で遊べても本格的にコードを書く方法知らないし. だって `exit()` したら前に書いたコードや結果保存されないじゃん？ ということで `M` 歩進めましょう.

# 簡易逆引き

こういうことをやりたいという欲求に基づいた Q&A の形式で書いていきます.

## ソースコードを書いてそれを実行したい.

`sample.jl` のように `.jl` を拡張子とするファイルにコードを記述します.

```julia:sample.jl
#=
複数行のコメントをかける．
あらかじめ `using Pkg; Pkg.add("Example")` を実行しておく.
=#
using Example
# Hello, World を出力する
println(hello("World"))
```

`julia sample.jl` のようにして実行します.

```console
$ julia sample.jl
Hello, World

```

### `ERROR: LoadError: UndefVarError: versioninfo not defined` が出るんだけれど

初心者殺し感があるので補足. `using InteractiveUtils` を記述する必要があります. Julia の REPL を起動時にはこれが暗黙のうちに行われているので使うことができます.

### version だけ知りたい

```julia
$ julia --version
julia version 1.8.3
```

### REPL の起動がめんどいな

下記のようにして `-e` を使うと良い:

```console
$ julia -e 'using InteractiveUtils; versioninfo()'
Julia Version 1.8.3
Commit 0434deb161e (2022-11-14 20:14 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin21.4.0)
  CPU: 16 × Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-13.0.1 (ORCJIT, skylake)
  Threads: 1 on 16 virtual cores
Environment:
  JULIA_EDITOR = subl
  JULIA_PROJECT = @.
```

`-E` を使うと最後の式を評価した値を出してくれます．

```console
$ julia -e '1+1' # こっちは出ない
$ julia -E '1+1' # 2 を表示できる
2
```

## ソースコードに書いて実行すると UnicodePlots.jl の結果が表示されない．

次のように `display` を使う必要があります.

```julia:sample.jl
using UnicodePlots
display(histogram(randn(100000)))
```

## 起動が遅い

ソースを更新して `julia file.jl` のように書いていると JIT コンパイルが毎回走りその実行コストが目立ってしまいます． したがって Python のようなスクリプトを開発するのと同様に

```
1．　ソースコード `sample.jl` を変更 
2． `julia sample.jl` を実行する
3. 最初に戻る
```

を行うのは開発効率の観点からはとても悪いです．

頻繁にコードを変更することになる開発時は基本的には REPL を起動しておいて作業に必要なパッケージをロードしたあとゴニョゴニョしながらコードを書いて REPL 上で実行するのが良いです． つまり REPL を `julia>` をプロンプトとするシェルと思って作業をするわけです． 
## ワークフローが知りたい

例えば [Revise.jl](https://timholy.github.io/Revise.jl/stable/) を使う運用があります.

ここでは下記のサンプルコード (`sample.jl`) を使って開発のフローを見ていきます．

```julia:sample.jl
using UnicodePlots

function f(N)
    histogram(randn(N))
end
```

まず Revise.jl のありがたみを知るために Revise.jl を使わない方法を紹介します．

### Revise.jl の運用の前に

`include` を使うことで `sample.jl` に書いた内容を REPL に反映させることができます.

```julia
julia> include("sample.jl") # 初回実行時は `using UnicodePlots` の時間がかかる.
julia> include("sample.jl") # 2 回目はコンパイル結果がキャッシュされるのでサクサク進む
julia> f(1000) # sample.jl で定義している f を使うことができる.
                ┌                                        ┐
   [-4.0, -3.0) ┤▎ 1
   [-3.0, -2.0) ┤██▏ 20
   [-2.0, -1.0) ┤█████████████▌ 135
   [-1.0,  0.0) ┤████████████████████████████████▍ 322
   [ 0.0,  1.0) ┤███████████████████████████████████  348
   [ 1.0,  2.0) ┤██████████████▍ 142
   [ 2.0,  3.0) ┤███▏ 30
   [ 3.0,  4.0) ┤▎ 2
                └                                        ┘
                                 Frequency

julia>
```

さて関数 `f` は `N` を受け取り1次元の標準正規分布に従うサンプルを `N` 個数生成し生成されたデータのヒストグラムを作成しています． 気が変わって一様分布を生成するようにしたいとします. `randn` の部分を `rand` にすればいいわけです. 好きなエディタを開いて下記のように編集します:

```julia:sample.jl
using UnicodePlots

function f(N)
	histogram(rand(N))
end
```

この時起動している Julia の REPL は閉じずそのままにしておくことに注意します.

`sample.jl` を更新・保存したあと REPL に戻って `include("sample.jl")` を再度実行します.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/a79a1fad-e12b-149d-9f32-1288f95de4d6.png)

そうすると一様分布に従うサンプルデータのヒストグラムを表示することができます.

`julia> include("sample.jl"); f(1000)` のようにセミコロンで区切ることで複数行の命令を１行で書くことができます． `Ctrl-P` で REPL で書いたコードの履歴を辿ることができます. `f(1000)` の実行は初回実行に比べるとサクサク動作します． これは `histogram` 関数のコンパイル結果を使い回しているからです.

#### シェルモードの活用

ちなみに `julia>;` で `shell>` というプロンプトに変わりシェルモードに遷移することができます. ここで `ls` や `cat sample.jl` など基本的なコマンドを Julia の REPL を終了しなくても使うことができます. `Ctrl-C` やバックスペースで `shell>` から `julia>` に戻ります．

つまり, ターミナルベースのエディタを使える環境・スキルがあれば

```julia
1. `julia>` から `shell>` に以降
2. 好きなエディタで編集
3. `shell>` から `julia>` に戻る
4. `julia> include("sample.jl"); f(1000)`
5. 1. に戻る
```

という運用もできます.

### Revise.jl の運用

`include("sample.jl")` を使うのは更新したソースコードを REPL に反映させるためです. `include` を使わなくてもソースの変更した箇所を検知して自動的に内容を REPL に反映させたいでしょう． Revise.jl を使うとできます. Revise.jl をインストールしましょう. 下記のようにして行うことができるのでした:

```julia
julia> using Pkg; Pkg.add("Revise")
```

一旦 REPL を閉じて再度 julia の REPL を起動し直します. そして `using Revise` を実行します:

```console
julia> using Pkg; Pkg.add("Revise")
julia> exit()
$ julia # もう一回遊ぶどん
             _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.8.3 (2022-11-14)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> using Revise
```

`include` の代わりに `includet` が使えるようになります. `help?>` を見る限り末尾の `t` はおそらく `track` から来ていると思われます. `sample.jl` が下記のようになっていることを確認しておきます. 

```julia
using UnicodePlots

function f(N)
	histogram(rand(N))
end
```

そして `includet("sample.jl")` を実行します.

ここまでの作業で下記のような画面になっているはずです．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/0c23e093-bf0d-a9f6-4289-27c0f07e7c99.png)

もちろん `f(1000)` とすることで一様分布のヒストグラムが表示されます．

```julia
julia> f(1000) # もっとサンプル数増やしていいかも
              ┌                                        ┐
   [0.0, 0.1) ┤██████████████████████████▉ 91
   [0.1, 0.2) ┤███████████████████████████████████  118
   [0.2, 0.3) ┤█████████████████████████████████▌ 113
   [0.3, 0.4) ┤█████████████████████████████▍ 99
   [0.4, 0.5) ┤█████████████████████████████▏ 98
   [0.5, 0.6) ┤██████████████████████████████▊ 104
   [0.6, 0.7) ┤█████████████████████████████▋ 100
   [0.7, 0.8) ┤█████████████████████████████▋ 100
   [0.8, 0.9) ┤██████████████████████████▋ 90
   [0.9, 1.0) ┤█████████████████████████▊ 87
              └                                        ┘
                               Frequency
```

さて，　再び `rand` から `randn` に戻しましょう. Julia の REPL は終了せずにコードを書き直します:

```julia:sample.jl
using UnicodePlots

function f(N)
	histogram(randn(N))
end
```

さて， ファイルを更新した後 `include`, `includet` をせずに `f(10000)` と実行してみましょう.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/9245d251-3ad3-5492-ec67-0943e403e723.png)

グラフの形が変わりましたね．コードの変更が反映されていることがわかります. 変更のほかに追記もできます. 例えば `greet` 関数を追加します．

```julia:sample.jl
using UnicodePlots

function f(N)
	histogram(randn(N))
end

function greet(msg)
	println("Hello" * msg)
end
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/a38e78a2-051d-1ef1-7a7f-b6ff2013dce0.png)

先ほどと同様に `include`, `includet` をせずに `greet("Jeff")` としましょう. Revise 側で `greet` の存在を検知しているのでユーザは何もせずに `greet` を使うことができます．

ここでは１ファイルで閉じる程度の小規模での例示でした． 実際にはそれなりに成長したパッケージの開発でも役に立ちます.

## パッケージを作りたい

ライブラリの開発もしたいと思うのでパッケージの開発のワークフローも紹介します． ここでは `MyPkg` という自作パッケージを作って `sample.jl` で試した `f` という関数をみんなに使ってもらうように整えることにします． つまり開発者であるあなたは他人(=未来の自分も含む)に対して下記のような使い方をしてもらうことを想定します.

```julia
julia> using MyPkg
julia> f(1000)
                ┌                                        ┐
   [-4.0, -3.0) ┤▎ 2
   [-3.0, -2.0) ┤█▉ 19
   [-2.0, -1.0) ┤██████████████▊ 144
   [-1.0,  0.0) ┤███████████████████████████████████  338
   [ 0.0,  1.0) ┤█████████████████████████████████▏ 319
   [ 1.0,  2.0) ┤███████████████▌ 150
   [ 2.0,  3.0) ┤██▋ 26
   [ 3.0,  4.0) ┤▎ 2
                └                                        ┘
                                 Frequency
```

### セットアップ

Pkg REPL によって `generate` を使用します． つまり下記のようにします.

```console
$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.8.3 (2022-11-14)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

(@v1.8) pkg> generate MyPkg
  Generating  project MyPkg:
    MyPkg/Project.toml
    MyPkg/src/MyPkg.jl
```

`tree` コマンドによって下記のようなディレクトリ構造が出来上がってること確認します.

```console
$ ls
MyPkg
$ tree
.
└── MyPkg
    ├── Project.toml
    └── src
        └── MyPkg.jl

2 directories, 2 files
```

### 開発をする

以下全て `cd MyPkg` をした場所にいると仮定します． `ls` をしたときに `Project.toml` がある場所と思ってください:

```console
$ cd MyPkg
$ ls
Project.toml src
```

さてこの場所で `$ julia --project=@.` という `--project` オプションをつけて REPL を起動します. これによって MyPkg プロジェクトをアクティベートした状態に以降することができます． これは Python でいえば `venv` や ,`pipenv`, `poetry` で特定の環境を作ることに対応しています． `--project` が面倒な場合は `JULIA_PROJECT=@.` のように環境変数を設定しておくと良いでしょう. オヌヌメです．

Pkg REPL に移行したプロンプトが `(MyPkg) pkg>` になっていることに注意します．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/b7230bbe-0100-ad1e-b979-004af7c6f39a.png)

### 依存関係の追加

さてここから MyPkg に必要なパッケージを入れます. `sample.jl` で定義していた `f` は `UnicodePlots` の機能に依存していました． したがって MyPkg を利用するユーザ・開発者は `UnicodePlots` のインストールも必要になります． その情報を加えるために `add UnicodePlots` をしましょう:

```julia
julia> using Pkg; Pkg.add("UnicodePlots") 
julia> # 下記の方でも良い
(MyPkg) pkg> add UnicodePlots
```

`cat Project.toml` をすると下記のようになっていると思います:

```
$ cat Project.toml
name = "MyPkg"
uuid = "12345xxxx" # <- ここはランダムに生成される
authors = ["YourName <your email>"]
version = "0.1.0"

[deps]
UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"
```

これで `UnicodePlots` に依存するということを宣言することができます． `Project.toml` は Python での `pyproject.toml` だと思ってください. `Manifest.toml` は Python における `poetry` の `poetry.lock` ファイルに相当します．

### コードを書く

依存パッケージを導入したのでロジックを書いていきます. `src/MyPkg.jl` を編集して下記のようにします.

```julia:MyPkg.jl
module MyPkg

using UnicodePlots
export f


"""
    f(N)
`N` 点サンプルを生成しそのヒストグラムを表示するよ
"""
function f(N)
	histogram(randn(N))
end

end # module MyPkg
```

これにより `MyPkg` というモジュールの中で `f` という関数を定義しました.

### 試す

自作パッケージ MyPkg を作ってみましょう.

```console
$ ls
Project.toml src
$ julia --project=@.
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.8.3 (2022-11-14)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> using MyPkg

julia> f(1000)
                ┌                                        ┐
   [-4.0, -3.0) ┤▎ 1
   [-3.0, -2.0) ┤██▍ 23
   [-2.0, -1.0) ┤██████████████▋ 144
   [-1.0,  0.0) ┤███████████████████████████████████  345
   [ 0.0,  1.0) ┤█████████████████████████████████▏ 326
   [ 1.0,  2.0) ┤██████████████▋ 144
   [ 2.0,  3.0) ┤█▌ 14
   [ 3.0,  4.0) ┤▍ 3
                └                                        ┘
                                 Frequency

```

`export f` という行を追加していたおかげで `using MyPkg` を行った時点で `MyPkg` モジュールで定義した `f` を使用することができます. 常に `MyPkg.f` として使う運用を想定する場合は `export f` の行を削除します.

### Revise.jl 再来

さてコードを書き直して `using MyPkg` をしても内容が反映されません. 一旦 Julia の REPL を終了する必要があります． 基本的に REPL を頻繁に起動するのは開発効率の観点からよろしくないです． Revise を使います． `using MyPkg` の前に `using Revise` をするだけです. これだけで OK. `includet` なども不要です.

```julia
julia> using Revise
julia> using MyPkg
```

`randn` を `rand` に置き換えてみました. コードの内容が更新され `f(1000)` のロジックが変わることがわかります．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/d25c85b2-4185-8d22-40dc-82ba9b269d11.png)

関数の追加も可能です. 例えばトーラスを描画する `g` を追加しましょう.

```julia
function g()
    torus(x, y, z, r=0.2, R=0.5) = (√(x^2 + y^2) - R)^2 + z^2 - r^2
    isosurface(-1:.1:1, -1:.1:1, -1:.1:1, torus, cull=true, zoom=2, elevation=50)
end
```

`MyPkg.g()` とすることで表示できます． `export g` をすれば `MyPkg.` は不要です.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/8431c340-7abe-0470-1312-02cc41adfa61.png)

ちなみに `f` の直前の文字列は `f` の docstring として解釈されるため `help?>` で確認できます(`julia>?f` と入力すること).

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/64685656-51d9-2a33-a09a-3cde0e5f9719.png)

さて(テストを書くなどやることはありますが)ここまでできれば最低限の道具で Julia によるコードを書いてパッケージを作ることができるようになります． 少なくとも `M` 歩は歩められるんじゃないかなと思います．

何を作るか， 書くかは皆さん次第です． 研究に使うコードをかいたら発表するのも良さそうですね:

https://www.youtube.com/watch?v=Sh_jBtP7RVY