モチベーション
Clojureの実行速度だと困ることが多々あるので、Juliaを試している。
Pythonのnumpyで遅すぎるということはないが、オブジェクト指向が起因でデータ構造が入り乱れる (リストとpandasとnumpyのように) のが好みでないし、REPLをより高速に回せるのであればそれに越したことはない。Common Lispのリストならば単一のデータ構造で何でもできるが、Clojureと同様にやはり遅くなってしまう1。
メモ
-
文字列はダブルクオートで、シングルクオートは不可。
-
mapをゴリゴリ利用しても速い。Python/numpyでもここまで速くはないし、こうも自然にmapは利用できないはず。- 「①抽象データ型 (シーケンス) と②シーケンス操作関数だけで何でもやろう」「自作変数も自作関数も少なくて短い方が良い」というClojureのシンプル精神は、Julia上でもある程度までは実現可能に思う。イミュータブル・遅延評価は再現が難しい2けれど、無くても実用上は3そこまで問題にならず、速度とのトレードオフとしては良い選択。
- バクを取るためにコードを書いているわけではないように、最適化をするためにコードを書いているわけではない。
-
map/filterはClojureと同じ感覚で使える。applyを使いたい場合はリスト内包表記でごまかす。 -
map内でif文を使いたいときは、do構文を利用して見た目を整える。-
doは無名関数をmapの第一引数に渡す。filterにも使用できる。
map([A, B, C]) do x if x < 0 && iseven(x) return 0 elseif x == 0 return 1 else return x end end -
- 無名関数は
x -> x + 3で(fn [x] (+ x 3))。 - 引数を1つしか取らない関数については、スレッド的なことをするパイプ演算子がある
12 |> x -> x + 3 #=> 15。 - 無名関数を使えば、複数の引数が必要な関数についてもパイプ演算子が使える。
10 |> x -> Dict(:ten => x) # Dict{Symbol, Int64} with 1 entry: # :ten => 10
-
関数を閉じるために
endを使う言語に忌避感を持っていたが、コード内で関数をまとめておけば、あまり気にならない。 -
デフォルト引数は、名前を書いて渡せない。Pythonと違うところ。
function hoge(a, b=2; c=3) println(a, b, c) end hoge(1, 2) # 123 期待通り hoge(1, 3) # 133 期待通りでない hoge(1, 2, 3) # ERROR (キーワード引数cへ、名前を書かずに渡している) hoge(1, b=2) # ERROR (デフォルト引数bへ、名前を書いて渡している) hoge(1, c=3) # 123 期待通り hoge(1, 2, c=3) # 123 期待通り hoge(1, b=2, 3) # ERROR (デフォルト引数bへ、名前を書いて渡している) hoge(1, b=2, c=3) # ERROR (キーワード引数cへ、名前を書かずに渡している) hoge(1, b=2; 3) # ERROR (いずれにしろ、セミコロンより後ろは名前を書いて渡す必要がある) -
関数へのデータ渡しは、基本的に参照渡し (共有渡し)。
-
ifelse文は便利だが、分岐に関わらずtrue節・false節の両方が評価されてしまう。 -
or/andではなく、||/&&。 -
while内でカウンタを進める際、配列は1始まりなので注意。a = [1, 2, 3, 4, 5] # こうすることが多いはず i = 1 while i < stop print(a[i]) i += 1 end # これはエラー i = 0 while i < stop print(a[i]) i += 1 end -
関数内で
returnを書かなくても、最後に評価される値が返される。関数型言語的。 -
三項演算子は連結できる
false ? 1 : false ? 2 : 3 #=> 3。 -
データ型は下記 (参考)。
- 数字 (整数、小数、複素数、分数)
- Bool (
true/false) - 文字 ('a', 'A')
- 文字列 ("string"):
*で文字列結合、$(var)で変数展開- 文字列→小数:
parse(Float64, "3.14") #=> 3.14 - 部分文字列:
chop("string", head=1, tail=2) #=> "tri"
- 文字列→小数:
- タブル: Pythonと同じ。
- 辞書:
Dict(key=>val)で生成。- Pythonの
:と異なり、=>と2文字も打つのが手間。辞書のキーに文字列を使うと、ダブルクオートを打つのが手間。良いマナーかは調べが付かなかったが、シンボルをキーとしたほうが楽。d = Dict(:name => "Julia", :version => 1.8) d[:name] # "Julia"
- Pythonの
- 集合: Pythonと同じ。
- 範囲オブジェクト:
start:stopかstart:step:stopの形式。 - 配列
-
[1 2 3](1×3 Matrix{Int64}) でも、[1; 2; 3](3-element Vector{Int64})でも、[1, 2, 3](3-element Vector{Int64})でも生成できる。- Vectorは Nx1 Matrix の別称。なので
[1, 2] + [1, 2] == [2, 4]だが、[1 2] + [1, 2]は行列計算ができずにエラー。
- Vectorは Nx1 Matrix の別称。なので
-
行列の積は
[1 2] * [3, 4] == 11。行列の要素ごとの積 (アダマール積) は[1, 2] .* [3, 4] == [3, 8]のように.を付ける。 -
Pythonと異なり、スライスの終わりを行列サイズ以上にはできない。
a = [1, 2, 3]; a[1:100]はエラー。 -
配列は1始まりなので、Pythonと異なり
stop-startの値が行列のサイズとは等しくならない。a[2:4]には、値が3個入っている。 -
例えば1次元と2次元どちらかの配列を列方向で半分に区切るような関数は、
if文を使わずsizeを使い、常に2次元を扱うような切り出し方をする。- Pythonのように
int(1.5)でint型にはできない。JuliaでIntを使う際は、中の小数はx.0になっている必要がある。 - このやり方をすると、Vector型を分けたときにMatrix Nx1型で返ってくる。実行速度に差異はあるのだろうが、今の所、実用上は問題を感じていない4。Vector型しか受け付けないような関数があった場合、
vec()関数でVector型に戻すこともできる。
function half(a) l = size(a)[1] / 2 a[1:Int(ceil(l)), :], a[Int(ceil(l))+1:end, :] end half([1, 2, 3, 4, 5]) # ([1; 2; 3;;], [4; 5;;]) half([1 2; 3 4; 5 6]) # ([1 2; 3 4], [5 6]) half([1, 2, 3, 4, 5])[1] + [1, 1, 1] # 3×1 Matrix{Int64}: # 2 # 3 # 4 - Pythonのように
-
Pythonと異なり、配列スライスの終わりのインデックスが結果に含まれる5。
# Python a = [1, 2, 3, 4, 5] a[0:3], a[3:] # ([1, 2, 3], [4, 5]) a[1:1] # []# Julia a = [1, 2, 3, 4, 5] a[1:3], a[4:end] # ([1, 2, 3], [4, 5]) a[1:1] # 1 -
配列が入った配列・タプル
a = [[1, 2, 3], [4, 5, 6]]を二次元配列[1 2 3; 4 5 6]にするには、stack(a, dims=1)→参考 (英語)。-
mapは結果を引数に合わせて配列かタプルに入れて返すので、処理した複数のベクタをstackでくっつけられる。map(x -> [x, x, x], [1, 2, 3]) # 3-element Vector{Vector{Int64}}: # [1, 1, 1] # [2, 2, 2] # [3, 3, 3] using DataFrames # あるいは、Julia 1.9からは標準で使えるらしい stack(map(x -> [x, x, x], [1, 2, 3]), dims=1) # 3×3 Matrix{Int64}: # 1 1 1 # 2 2 2 # 3 3 3
-
-
配列の列ごと・行ごとの値の合計は、
sum(a, dims=n)。n=1で行の和、n=2で列の和。 -
配列の最大値は
maximum。maxではない。
-
-
タプルとジェネレータ式で模倣できるように思われる。それにミュータブルがもたらす混乱については、破壊的な関数に
!を付ける慣習だけで、(小規模なコードであれば) 対応できる。 ↩ -
哲学的には大問題かもしれない。 ↩
-
『パフォーマンスのためにベクトル化する必要がない。ベクトル化しないコードも速い』, Julia 1.0 ドキュメント, https://mnru.github.io/julia-doc-ja-v1.0/index.html ↩
-
そもそもPythonとJuliaで配列の数え方が違うのでこの言い方はおかしいけれど、こう言いたくなる。 ↩