本日は
Julia アドベントカレンダーとして投稿です.
Julia言語 を導入し人生を N ≥ 1 歩進める(2022 年 12 月 版) の続きとして書きます. したがって N ≥ 1
歩人生を進めていることを仮定します. つまり Julia を導入して REPL で遊べるようになったとします.
さて N
歩進んで次に進むべき場所がわからない. だって REPL で遊べても本格的にコードを書く方法知らないし. だって exit()
したら前に書いたコードや結果保存されないじゃん? ということで M
歩進めましょう.
簡易逆引き
こういうことをやりたいという欲求に基づいた Q&A の形式で書いていきます.
ソースコードを書いてそれを実行したい.
sample.jl
のように .jl
を拡張子とするファイルにコードを記述します.
#=
複数行のコメントをかける.
あらかじめ `using Pkg; Pkg.add("Example")` を実行しておく.
=#
using Example
# Hello, World を出力する
println(hello("World"))
julia sample.jl
のようにして実行します.
$ julia sample.jl
Hello, World
ERROR: LoadError: UndefVarError: versioninfo not defined
が出るんだけれど
初心者殺し感があるので補足. using InteractiveUtils
を記述する必要があります. Julia の REPL を起動時にはこれが暗黙のうちに行われているので使うことができます.
version だけ知りたい
$ julia --version
julia version 1.8.3
REPL の起動がめんどいな
下記のようにして -e
を使うと良い:
$ 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
を使うと最後の式を評価した値を出してくれます.
$ julia -e '1+1' # こっちは出ない
$ julia -E '1+1' # 2 を表示できる
2
ソースコードに書いて実行すると UnicodePlots.jl の結果が表示されない.
次のように display
を使う必要があります.
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 を使う運用があります.
ここでは下記のサンプルコード (sample.jl
) を使って開発のフローを見ていきます.
using UnicodePlots
function f(N)
histogram(randn(N))
end
まず Revise.jl のありがたみを知るために Revise.jl を使わない方法を紹介します.
Revise.jl の運用の前に
include
を使うことで sample.jl
に書いた内容を REPL に反映させることができます.
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
にすればいいわけです. 好きなエディタを開いて下記のように編集します:
using UnicodePlots
function f(N)
histogram(rand(N))
end
この時起動している Julia の REPL は閉じずそのままにしておくことに注意します.
sample.jl
を更新・保存したあと REPL に戻って include("sample.jl")
を再度実行します.
そうすると一様分布に従うサンプルデータのヒストグラムを表示することができます.
julia> include("sample.jl"); f(1000)
のようにセミコロンで区切ることで複数行の命令を1行で書くことができます. Ctrl-P
で REPL で書いたコードの履歴を辿ることができます. f(1000)
の実行は初回実行に比べるとサクサク動作します. これは histogram
関数のコンパイル結果を使い回しているからです.
シェルモードの活用
ちなみに julia>;
で shell>
というプロンプトに変わりシェルモードに遷移することができます. ここで ls
や cat sample.jl
など基本的なコマンドを Julia の REPL を終了しなくても使うことができます. Ctrl-C
やバックスペースで shell>
から 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> using Pkg; Pkg.add("Revise")
一旦 REPL を閉じて再度 julia の REPL を起動し直します. そして using Revise
を実行します:
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
が下記のようになっていることを確認しておきます.
using UnicodePlots
function f(N)
histogram(rand(N))
end
そして includet("sample.jl")
を実行します.
ここまでの作業で下記のような画面になっているはずです.
もちろん f(1000)
とすることで一様分布のヒストグラムが表示されます.
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 は終了せずにコードを書き直します:
using UnicodePlots
function f(N)
histogram(randn(N))
end
さて, ファイルを更新した後 include
, includet
をせずに f(10000)
と実行してみましょう.
グラフの形が変わりましたね.コードの変更が反映されていることがわかります. 変更のほかに追記もできます. 例えば greet
関数を追加します.
using UnicodePlots
function f(N)
histogram(randn(N))
end
function greet(msg)
println("Hello" * msg)
end
先ほどと同様に include
, includet
をせずに greet("Jeff")
としましょう. Revise 側で greet
の存在を検知しているのでユーザは何もせずに greet
を使うことができます.
ここでは1ファイルで閉じる程度の小規模での例示でした. 実際にはそれなりに成長したパッケージの開発でも役に立ちます.
パッケージを作りたい
ライブラリの開発もしたいと思うのでパッケージの開発のワークフローも紹介します. ここでは MyPkg
という自作パッケージを作って sample.jl
で試した f
という関数をみんなに使ってもらうように整えることにします. つまり開発者であるあなたは他人(=未来の自分も含む)に対して下記のような使い方をしてもらうことを想定します.
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
を使用します. つまり下記のようにします.
$ 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
コマンドによって下記のようなディレクトリ構造が出来上がってること確認します.
$ ls
MyPkg
$ tree
.
└── MyPkg
├── Project.toml
└── src
└── MyPkg.jl
2 directories, 2 files
開発をする
以下全て cd MyPkg
をした場所にいると仮定します. ls
をしたときに Project.toml
がある場所と思ってください:
$ cd MyPkg
$ ls
Project.toml src
さてこの場所で $ julia --project=@.
という --project
オプションをつけて REPL を起動します. これによって MyPkg プロジェクトをアクティベートした状態に以降することができます. これは Python でいえば venv
や ,pipenv
, poetry
で特定の環境を作ることに対応しています. --project
が面倒な場合は JULIA_PROJECT=@.
のように環境変数を設定しておくと良いでしょう. オヌヌメです.
Pkg REPL に移行したプロンプトが (MyPkg) pkg>
になっていることに注意します.
依存関係の追加
さてここから MyPkg に必要なパッケージを入れます. sample.jl
で定義していた f
は UnicodePlots
の機能に依存していました. したがって MyPkg を利用するユーザ・開発者は UnicodePlots
のインストールも必要になります. その情報を加えるために add UnicodePlots
をしましょう:
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
を編集して下記のようにします.
module MyPkg
using UnicodePlots
export f
"""
f(N)
`N` 点サンプルを生成しそのヒストグラムを表示するよ
"""
function f(N)
histogram(randn(N))
end
end # module MyPkg
これにより MyPkg
というモジュールの中で f
という関数を定義しました.
試す
自作パッケージ MyPkg を作ってみましょう.
$ 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> using Revise
julia> using MyPkg
randn
を rand
に置き換えてみました. コードの内容が更新され f(1000)
のロジックが変わることがわかります.
関数の追加も可能です. 例えばトーラスを描画する g
を追加しましょう.
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.
は不要です.
ちなみに f
の直前の文字列は f
の docstring として解釈されるため help?>
で確認できます(julia>?f
と入力すること).
さて(テストを書くなどやることはありますが)ここまでできれば最低限の道具で Julia によるコードを書いてパッケージを作ることができるようになります. 少なくとも M
歩は歩められるんじゃないかなと思います.
何を作るか, 書くかは皆さん次第です. 研究に使うコードをかいたら発表するのも良さそうですね: