11
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

JuliaLangAdvent Calendar 2022

Day 10

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

Last updated at Posted at 2022-12-09

本日は

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

Julia言語 を導入し人生を N ≥ 1 歩進める(2022 年 12 月 版) の続きとして書きます. したがって N ≥ 1 歩人生を進めていることを仮定します. つまり Julia を導入して REPL で遊べるようになったとします.

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

簡易逆引き

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

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

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

sample.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 を使う必要があります.

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 を使う運用があります.

ここでは下記のサンプルコード (sample.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>

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

sample.jl
using UnicodePlots

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

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

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

image.png

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

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

シェルモードの活用

ちなみに julia>;shell> というプロンプトに変わりシェルモードに遷移することができます. ここで lscat 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") を実行します.

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

image.png

もちろん 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 は終了せずにコードを書き直します:

sample.jl
using UnicodePlots

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

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

image.png

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

sample.jl
using UnicodePlots

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

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

image.png

先ほどと同様に 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> になっていることに注意します.

image.png

依存関係の追加

さてここから MyPkg に必要なパッケージを入れます. sample.jl で定義していた fUnicodePlots の機能に依存していました. したがって 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 における poetrypoetry.lock ファイルに相当します.

コードを書く

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

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

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

image.png

関数の追加も可能です. 例えばトーラスを描画する 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. は不要です.

image.png

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

image.png

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

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

11
7
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
11
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?