LoginSignup
23
14

More than 1 year has passed since last update.

Julia言語 を導入し人生を N ≥ 1 歩進める(2022 年 12 月 版)

Last updated at Posted at 2022-12-01

本日は

アドベントカレンダーの2日目です

プログラミング言語 Julia の導入をします.

インストール

適当なシェル環境で julia と入力したら下記のような対話環境を出力することを目標にします.

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

概ね下記の方法1, 方法2のどちらかで行えば良いです.個人的には方法2 を使っています.

方法1: 手動でバイナリを入手する方法

バイナリは https://julialang.org/ から入手することができます. ダウンロードしたバイナリのパスを PATH 環境変数に追加すれば動作します. 具体的なやり方は使用する OS 毎の説明がある https://julialang.org/downloads/platform/ をご覧ください.

方法2: Cross-platform installer を用いる方法

juliaup を用いた場合

juliaup を使うと新しい Julia のバージョンがリリースされた際にわざわざバイナリを手動で入手することなく juliaup コマンドを経由して使用するバージョンを切り替えることができます.Linux 環境では juliaup の README の通りにすると導入することができます:

curl -fsSL https://install.julialang.org | sh

以下のスクリーンショットは GCP の Cloud Shell で行ったものをキャプチャしたものです.

image.png

特にこだわりがなければ Proceed with installation のまま Enter を押して進めます.

image.png

julia を入力して使えることを確認します.

特定のバージョン,例えば LTS 扱い の 1.6 系を導入する場合は

$ # 1.6 をインストール
$ juliaup add 1.6
$ # バージョンは切り替わらない
$ julia --version
julia version 1.8.3
$ # 暫定的に使う場合
julia +1.6
$ # 1.6 を既定にする場合
$ juliaup default 1.6
$ # 元に戻す場合
$ juliaup default 1.8

とはいえ 1.6 とか 1.8 というような数値は覚えたくないと思うので

$ # 安定リリース版を使う場合
$ juliaup default release
$ # LTS 版を使う場合
$ juliaup default lts

を覚えておけば良いです.特にこだわりがなければ安定最新版(release) の方を使うと良いです.

Windows の場合

Windows 環境ですと Microsoft Store からインストールすると juliaup というコマンドと julia が使えるようになります.

image.png

winget を使っても入手できるようです.winget は Windows 11 ではデフォルトで入っているようなので導入が楽になっています.

winget install julia -s msstore

jill を用いた方法

普段は Python を使っていて juliaup の導入時に curl ... コマンドが覚えられない. そんな人は jill を使うのもありでしょう:

$ pip3 install jill
$ jill install 1.8

REPL で遊ぶ

インストールが完了できれば julia コマンドによって下記のような対話環境が得られます.これを REPL (read-eval-print loop) と呼びます.

image.png

Julia は Python のようにインデントに関してセンシティブではないので巷にあるサンプルコードをコピーして REPL にペーストしてコードを実行させることができます. 例えば下記の julia> も含んだコードは REPL 上で動かすことを念頭に置いています.julia> の部分を丸ごとコピーして貼り付けても REPL 側でよしなに処理してくれます.

julia> msg = "Hello"
"Hello"

julia> println(msg)
Hello

julia> (.*)(1:9, (1:9)') # ブロードキャストで九九表を作成
9×9 Matrix{Int64}:
 1   2   3   4   5   6   7   8   9
 2   4   6   8  10  12  14  16  18
 3   6   9  12  15  18  21  24  27
 4   8  12  16  20  24  28  32  36
 5  10  15  20  25  30  35  40  45
 6  12  18  24  30  36  42  48  54
 7  14  21  28  35  42  49  56  63
 8  16  24  32  40  48  56  64  72
 9  18  27  36  45  54  63  72  81

Julia の REPL は便利な機能が含まれています.時間のある方はYouTubeにある Julia のワークショップが参考になります.

こんなこともできます.

簡易逆引き

これをするにはどすればいい?というのを簡単にですがまとめます. (スペースの都合で余分な改行は省略しています.)

Hello World

julia> msg = "Hello World" # "Hello World" を `msg` に紐づける
"Hello World"

julia> msg * " " * "Julia" # 文字列の結合は `*` を使う
"Hello World Julia"

julia> println(msg)
Hello World

julia> print(msg); print(" "); print("Julia")
Hello World Julia
julia> name = "Goma"; "Hello $name"
"Hello Goma"
  • println 出力の最後に改行を追加します. print は改行を含めません.
  • # の隣には Python と同様にコメントを書くことができます.
  • "$(name)"name の値を補間することができます(string-interpolation):

  • セミコロンを挟むことで複数行のコードを1行にまとめることができます.

簡易的な電卓として

$x=3$ のときに $2x+5$, $x^2$ を計算したい場合はほぼそのままコードに落とし込むことができます.

julia> x = 3
3
julia> 2x + 5
11
julia> 2 * x + 5
11
julia> x ^ 2 # x の二乗を計算
9
julia> 5 / 2
2.5
julia> div(5, 2) # これは 5 を 2 で割った商を出す
2

数式を数式らしくコードにしたい

Julia は Unicode 文字を変数名に採用することができ入力を支援する機能がそなわっています. 円周率 $\pi$ は pi でも使うことができますが \pi のあとTABキーを入力することで $π$ を使うことができます. $\LaTeX$ を触ったことがある方は習熟しやすいでしょう.

julia> θ = π/6 # \theta
0.5235987755982988

julia> sin(2θ)  2sin(θ)*cos(θ)
true

↑の は何?

isapprox 関数のエイリアスです. \approx + TAB で入力ができます.
それを調べるには REPL で ?≈ を入力するとヘルプモードにアクセスします:

image.png

例えば下記のコードは Julia の文法として意味のあるコードです.

julia> 3  [1, 2, 3]  [3, 4, 5]
true

julia> x₁ = 1; x₂ = 2; x₃ = 3
3

julia> 𝐱 = [x₁, x₂, x₃]
3-element Vector{Int64}:
 1
 2
 3

関数とかループとか条件分岐とか知りたい

乱数を振って円周率を計算する例で全部例示できます.

julia> function calcpi(N)
           cnt = 0
           for i in 1:N
               x = rand()
               y = rand()
               if x^2 + y^2 < 1
                   cnt += 1
               end
           end
           4cnt/N
       end
calcpi (generic function with 1 method)

julia> calcpi(10000)
3.14
  • function calcpi(N)... endcaclpi という名前の関数名を定義できます.
    • return キーワード がないですが最後に評価した式の値を返す仕様になっています.もちろんつけても OK です. つけるかつけないかは個々の趣味やチーム毎のコーディング規約に依存します.
  • calcpi(10000) で呼び出せます. for i in 1:N ... end で繰り返し処理を書きます.
  • 条件分岐は if 条件式 ... end で書けます.
    julia> 1 && true
    ERROR: TypeError: non-boolean (Int64) used in boolean context
    

ライブラリを使ってみたい.

導入

パッケージと呼ぶことが多いです. 例を示すために Example.jl パッケージ を導入してみましょう.

julia> using Pkg; Pkg.add("Example")

Pkg.add("Example")Example.jl パッケージをインストールすることができます.Pkg は Julia に組み込みされているパッケージマネージャ(の機能を扱うパッケージ)です.

https://pkgdocs.julialang.org/v1/
https://docs.julialang.org/en/v1/stdlib/Pkg/

使う

using Example を実行すると Example.jl パッケージオブジェクトに提供されている hello 関数を使うことができます.

julia> using Example

julia> Example.hello("Goma")
"Hello, Goma"

julia> hello("Goma")
"Hello, Goma"

using Example と実行することで Example.hello とするだけでなく hello を使うことができています. Example.jl 内部で定義している名前・識別子を現在の(グローバルの)名前空間に導入します.どのような名前を知るには Example.jl パッケージを読む必要があります.

export hello, domath

とあるので using Example によって hello と(実は) domath を現在の Julia のセッションに導入していることがわかります.

Python ユーザーにとってはちょっと気持悪いと感じるかもしれませんが, 慣れてくると少量のタイプ量で多くの機能にアクセスできる体験の良さがわかってきます. むしろ from ... import ... がめんどいと感じるようになるはずです.

どうしても導入される名前を制御したい

何かしらの理由で domath のみを導入したい場合, 特定の関数や値・型がどのパッケージから由来するのか明示したい場合は using Example の代わりに using Example: domath をすればOKです. hello 単体では使うことができないことが確認できるでしょう. 極論 using Example: Example とすれば Example 以外の名前を導入を抑えることができます.

ひとまずは using <パッケージ名> でOK

Julia では Example.hello みたいに仰々しくモジュール(ここでは Exampleのこと)名を付与することはしません.開発者は多重ディスパッチを使って同じ名前のメソッドを実装しユーザーはシンプルで柔軟なインターフェースを使って機能にアクセスすることができます. ユーザーとして Julia を使うのであれば using Plots とか using Distributions とか using Images みたいに using BenchmarkTools みたいに using を積極的に使うと良いです.

もう少し実用的なライブラリを触りたい.

REPL でグラフを描く: UnicodePlots.jl

関数のグラフを書いてみましょう.

julia> using UnicodePlots
julia> plt = lineplot(sin)
julia> lineplot!(plt, cos)
julia> lineplot!(plt, x-> 1 - 0.01x^2, name="Quadratic")

using UnicodePlots によって lineplot, lineplot! を使うことができます.$\sin$, $\cos$, $f(x) = 1 - 0.001x^2$ を描画できます.

image.png

関数の末尾に ! がある場合入力引数に影響を与えますよというプログラマの意図を読む側に伝える目的を持っています. 例えば $\sin$ を描画後 $\cos$ を描画する際にグラフオブジェクト plt の状態が変わります. 文法的に強制されているわけではないですがコーディング規約として用いられています.

x-> 1 - 0.01x^2 の代わりに f(x) = 1 - 0.001x^2f を定義して lineplot!(plt, f) でも OK です.

関数だけでなく座標の列を入力させることもできます.

julia> lineplot([1,2,3],[3,1,2])
     ┌────────────────────────────────────────┐
   3 │⠣⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
     │⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
     │⠀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
     │⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
     │⠀⠀⠀⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
     │⠀⠀⠀⠀⠀⠀⠘⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
     │⠀⠀⠀⠀⠀⠀⠀⠈⢢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
     │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠│
     │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠊⠀│
     │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠣⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠊⠁⠀⠀⠀│
     │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠁⠀⠀⠀⠀⠀⠀│
     │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀│
     │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠣⡀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
     │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⡄⠀⠀⠀⠀⡠⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
   1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⣀⠔⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
     └────────────────────────────────────────┘
     ⠀1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀3⠀

Julia の良さの一つに多重ディスパッチがあります.lineplot の名前さえ覚えておけばあとはなんとなく「こうすればいいだろ」という想像力を持って入力を与えておけば開発者が用意した適切なメソッドを呼び出してくれます.@which lineplot(sin) によって lineplot 関数に紐づくどの実装(メソッド)を呼ぶのかを人間に教えてくれます:

julia> @which lineplot(sin)
lineplot(f::Function; kw...) in UnicodePlots at ~/.julia/packages/UnicodePlots/Z7FG6/src/interface/lineplot.jl:223

julia> @which lineplot([1,2,3],[3,1,2])
lineplot(x::AbstractVector, y::AbstractVector) in UnicodePlots at ~/.julia/packages/UnicodePlots/Z7FG6/src/interface/lineplot.jl:65

これにより初見でも特定の識別子がどこからやってきたのかをある程度知ることができます.詳細には VSCode などでデバッガを起動して掘り下げていくことになります.

乱数発生をさせたい: Distributions.jl

一様乱数や標準正規分布に従う乱数を発生させるのはとても簡単です. 各々 rand, randn で生成できます.

julia> using UnicodePlots
julia> xs = rand(100000);
julia> ns = randn(100000);

末尾にセミコロンを入れると冗長な出力を抑えることができます. UnicodePlots.jl でサンプルのヒストグラムを可視化できます:

julia> histogram(xs)
julia> histogram(ns)

image.png

Distributions.jl は幅広い分布の種類をカバーします.

julia> using StatsPlots, Distributions
julia> unicodeplots() # おまじない
julia> plot(Exponential(0.1), linecolor=:red)
julia> plot(Laplace(0, 1), linecolor=:green)

StatsPlots.jl は Distributions.jl で定義されている分布の密度関数を可視化してくれます.

image.png

また下記の点にも注意しましょう:

julia> @which rand(100)
rand() in Random at /Applications/Julia-1.8.app/Contents/Resources/julia/share/julia/stdlib/v1.8/Random/src/Random.jl:278

julia> @which rand(Laplace(0, 1), 100)
rand(s::Sampleable, dims::Int64...) in Distributions at ~/.julia/packages/Distributions/gggmX/src/genericrand.jl:22

前者は前述の通り一様分布を後者はラプラス分布 Laplace(0, 1) に従うサンプルを 100 個生成するメソッドの定義場所を示しています. using Distributions をすることによって rand ができること(機能が増えていること)がわかります. 実は Distributions.jl は 標準で使える rand を拡張して機能を提供しています.これも Julia の多重ディスパッチの恩恵です.

シードを付与して乱数の発生を制御したい

実験の再現性などで擬似乱数の生成を決定的にしたい場合は
擬似乱数生成アルゴリズムとシード値を明示的に指定し擬似乱数アルゴリズムのインスタンスを rand に入力すればOKです.標準ライブラリの Random が提供する Xoshiro を使ってみましょう.

julia> using Random, Distributions

julia> rand(Xoshiro(0), Bernoulli(0.2), 5)
5-element Vector{Bool}:
 0
 1
 0
 1
 0

julia> rand(Xoshiro(0), Bernoulli(0.2), 5)
5-element Vector{Bool}:
 0
 1
 0
 1
 0

パフォーマンス計測

BenchmarkTools.jl パッケージがあります.

julia> @benchmark minimum(rand(1000))
julia> @benchmark minimum($(rand(1000)))

以下の例はランダムに生成した配列の最小値を求める関数のベンチマークをとっています.

image.png

前者は試行毎に rand(1000) が実行されます.従って乱数生成の時間と minimum 関数の計算の両方を含んでいます. 後者は rand(1000) の値を補間して minimum 側の計算にフォーカスしてベンチマークをとっています.

を眺めておくと良いでしょう.

@ほにゃらら はなに?

マクロの呼び出しをしています. マクロはコード断片を入力とし適切に変換されたコード生成します. 例えば Python での f-string の機能を使うと f"{x=}" x=<xの値> が出力されます. Julia では @show マクロを使うと実現できます.

julia> x = 3
3

julia> @show x
x = 3
3

@macroexpand というマクロを使うとマクロがどのようなコードを生成するのかを観察することができます:

julia> @macroexpand @show x
quote
    Base.println("x = ", Base.repr(begin
                #= show.jl:1047 =#
                local var"#13#value" = x
            end))
    var"#13#value"
end

大雑把にいえば x というコードの断片を入力することで下記のようなコードを生成・評価していることを意味しています.

println('x = ", x)
x

関数の1回あたりの実行時間を計測する @elapsed マクロについても調べてみましょう:

julia> @elapsed rand(100)
2.973e-6
julia> @macroexpand @elapsed rand(100)
quote
    #= timing.jl:380 =#
    $(Expr(:meta, :force_compile))
    #= timing.jl:381 =#
    local var"#21#t0" = Base.time_ns()
    #= timing.jl:382 =#
    rand(100)
    #= timing.jl:383 =#
    (Base.time_ns() - var"#21#t0") / 1.0e9
end

大雑把にいえば下記のようなコードを生成・評価していることがわかります.

s = time_ns()
rand(100) # これが入力したコードの断片
e = time_ns()
(e - s) / 1.0e9

配列

Julia は(デフォルトでは) 1 始まりです. Python や C/C++ のプログラムにどっぷり使ってると Julia に移植する際に微妙な混乱を引き起こします.

Python のコードで表現できる場合は 0 を使う場合は配列の最初の位置の値を取得する目的が多いと思います.その場合は下記のような表記で要素のアクセスを抽象化できます.

julia> arr = [-1,-2,-3]
3-element Vector{Int64}:
 -1
 -2
 -3

julia> arr[1]
-1

julia> arr[begin] # arr[end]
-1

julia> first(arr) # last(arr)
-1
  • 剰余系の完全代表系を 1 始まりにしたい場合は mod ではなく mod1 にすると良いです.
  • どうしても 1 始まりが気持ち悪い.コードを line by line で忠実に移植する必要があれば OffsetArrays.jl を使って添字の原点を調整することができます:
julia> using OffsetArrays: Origin

julia> oa = Origin(0)([1,2,3])
3-element OffsetArray(::Vector{Int64}, 0:2) with eltype Int64 with indices 0:2:
 1
 2
 3

julia> oa[0]
1

julia> oa[begin]
1

julia> first(oa)
1

まとめ

書いたらキリがないですが,入門時に知っておくと良いことを書いておきました.

Python 入門系は Jupyter Notebook の環境を構築して云々してようやくスタートに立てますが,Julia は REPL だけで色々遊ぶことができます.最近はパッケージのインストールの失敗で諦めるという事象は減ってきてると思うので特殊な環境でない入門では限り困らないと思います.

必要に応じて help?> を使ったりドキュメントを読んだり,ソースコードを追いかけたり Web 上の解説を読むと良いでしょう.

ちなみに Julia は 2009 年に開発がスタートし 2012 年に公開されました.ということは公開から10周年です.ちょうどいい節目を逃す前に記念にトライしてはいかがでしょうか?

23
14
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
23
14