モチベーション
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で配列の数え方が違うのでこの言い方はおかしいけれど、こう言いたくなる。 ↩