あのさ、時代はJuliaだよ?

あのさ、時代はJuliaだよ?

知らんけど。

はじめに

本当にざっと説明するだけです。

ですので歴史的背景を知りたかったり応用例を知りたい人には物足りないかもしれません。その場合はWikipediaにあります

Julia、読み方は「ジュリア」でそのままです。

「『ジュ』リャ」なのか「ジュ『リア』」なのか「ジュ『リ』ア」なのかは知らないけど私の場合、対日相手には2番目、対外相手には1番目で説明します。

友人が「Julia」でググってみた結果ではセクシー女優が出たらしいですが、私が検索してもそんなの出てこなかった(どちらが変態性に富んでいるのかという議論はしない)

「これ、分からなかったらググれば良いじゃん(Python3で言ったら最初は使わなくても開発くらいできるデコレーターとかジェネレーターとか)」という部分は適宜、端折ります。

このページのターゲット

最低限これくらいはできる人をターゲットとしています。

  • Python3もしくはR言語をなんとなくでも良いから書ける
  • オブジェクト指向をなんとなくでも良いから知っているしよく使う
  • 環境開発が嫌い
  • 分からなかったら自分でググったりできる

「Juliaとか言うPython3よりも実行速度が速い機械学習をしてみたい」という人はなお良いでしょう。Juliaで機械学習を使う方法についてはググって下さい。あくまでこのページはほんの触り程度なので。

このページでは主にPython3と比較して説明します。R言語ユーザーの方たち、すみません。

このページがターゲットとしていない人たち

書いとくべきでしょう。

  • プログラミング入門者
  • Python3で挫折した人

その気になればプログラミング入門者用のJulia紹介ページも作りたい(Python3ほど参考書が多いわけではないから)

私の環境

こんな感じ(2020年1月31日現在)

  • macOS Catalina 10.15
  • MacBook Air (13-inch, 2017)
  • Julia Version 1.3.1

Windowsユーザーや他のUNIX系ユーザーの方たちは、その都度ググって下さい。

また私はvimばかりを使っている人なので(正確にはneovimなんだけれど)、Jupyter Notebookを使っての説明はしません。「Jupyter Notebookを使いたいなあ」という人は、このQiitaにあります

環境設定

至ってシンプル。Qiitaにあった

$ brew cask install julia
...
🍺  julia was successfully installed!

とりあえずこう表示されたら、以下のコマンドを実行してみましょう。

$ julia

するとこう表示されるはずです。

               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.3.1 (2019-12-30)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |




julia> 

もっとカラフルに表示されます。

「本当にこれだけ?」と思うことでしょう。

本当にこれだけです(今のところエラー報告は聞いてない)

Python3のときにパスを通したりしなくても良いのは非常に好都合。

一応Juliaはこのようにコンパイル言語なので(だから実行速度が速い、ただしコンパイルには時間がかかる)、インタープリターと言って良いのかはわかりませんが、それっぽいものが提供されています。

演算子

参考書とかだと最初のほうにREPLとか書いてあるものが多いですが、とりあえず飛ばします。

「REPL? 何それ美味しいの?」と考えると思います。そういうのがある程度に考えといてください。

算術演算

とりあえずコードを書いてみましょう。

# println関数で結果を出力。「#」でコメントできる
# 文字列は「"」で囲む。「'」が使えないから不便
# 「,」で分けられるが、空白は入らない
println("Hello", "World!")




println("==== 算術演算 ====")
x = -3   # 代入
y = 5
println("+x        = ", +x)
println("-x        = ", -x)
println("x + y     = ", x + y)
println("x - y     = ", x - y)
println("x * y     = ", x * y)
println("x / y     = ", x / y)
println("div(x, y) = ", div(x, y))   # ÷ も使えるけどREPLで説明する
println("x ^ y     = ", x ^ y)
println("x % y     = ", x % y)




println("==== ビット演算 ====")
println("~x      = ", ~x)
println("x & y   = ", x & y)
println("x | y   = ", x | y)
println("x >>> y = ", x >>> y)   # 論理右シフト 最上位桁の値は0になる
println("x >> y  = ", x >> y)    # 算術右シフト 最上位桁の値は1になる
println("x << y  = ", x << y)




# 複合代入演算子 += などは省略




println("==== ブール演算 ====")
a = false   # true や false は小文字
b = true
println("!a        = ", !a)
println("a & b     = ", a & b)
println("a | b     = ", a | b)
println("xor(a, b) = ", xor(a, b))  # ⊻ も使えるけどREPLで説明する
println("x == y    = ", x == y)
println("x != y    = ", x != y)
println("x < y     = ", x < y)
println("x <= y    = ", x <= y)
println("x > y     = ", x > y)
println("x >= y    = ", x >= y)
z = 8
println("x < y < z = ", x < y < z)

実行するとこんな感じ

$ julia sample.jl
HelloWorld!
==== 算術演算 ====
+x        = -3
-x        = 3
x + y     = 2
x - y     = -8
x * y     = -15
x / y     = -0.6
div(x, y) = 0
x ^ y     = -243
x % y     = -3
==== ビット演算 ====
~x      = 2
x & y   = 5
x | y   = -3
x >>> y = 576460752303423487
x >> y  = -1
x << y  = -96
==== ブール演算 ====
!a        = true
a & b     = false
a | b     = true
xor(a, b) = true
x == y    = false
x != y    = true
x < y     = true
x <= y    = true
x > y     = false
x >= y    = false
x < y < z = true

本当は「\(バックスラッシュ)演算子いらん」とかあるけど、詳しく知りたかったらググってください。

if for whileなど

ちゃんと用意されています。

コード例はこんな感じ。

println("==== for i in 5 ====")
for i in 5
  println(i)
end
# Ruby っぽく end を書く

#= Python3 だと range(1, 5+1)
むしろR言語に近い書き方
そういえばコメントアウトは「#=」と「=#」で囲む方法もあるし、
C/C++の「/* /* comment */ error */」とは違って多重で囲んでもエラーを起こさない
=#
println("==== for i in 1:5 ====")
for i in 1:5
  println(i)
end

# Python3 だと range(1, 5+1, 2)
println("==== for i in 1:2:5 ====")
for i in 1:2:5
  println(i)
end

# in は ∈ とかいう文字で代用できるがREPLで説明する
for obj in ["Hello", '%', 789]
  println(obj)
end

i = 5
while true
  global i -= 1
  if i == 0
    break
  # else if は elif でも elsif でもない
  elseif i % 2 == 1
    println("$i is an odd number.")   # $i で i の結果が出力できる。Python3 なら f'{i} is an odd number.'
  else
    println("$i is an even number.")
  end
end

x, y = 3, 5   # このようにしてもそれぞれの変数に数値が代入される
# 三項演算子
println("x == y ? 'y' : 'n' = ", x == y ? 'y' : 'n')

で結果。

==== for i in 5 ====
5
==== for i in 1:5 ====
1
2
3
4
5
==== for i in 1:2:5 ====
1
3
5
Hello
%
789
4 is an even number.
3 is an odd number.
2 is an even number.
1 is an odd number.
x == y ? 'y' : 'n' = n

Goとは違ってwhileはあります(私的にwhileはいらないと思うけど、コンピューターが解釈しやすいように残してあるんだと思う)

try catchは後ほど(私のほうではあまり説明していないけど)

演算子の章において、x >>> yの結果を見てみるとわかりますが、オーバーフローしてます。

実は一般的な整数型はInt64型で宣言されているため、このような問題が発生します。

もし大きな数を使いたい場合は、BigInt型を使うことになります。

以下プログラムと実行例。

for mio_obj in [
  "hogehoge", '@', '紙',
  3, -1,
  2.88, pi, exp, 3 // 12, 0o755, 0xff,
  1e5, 10 ^ 20, BigInt(10) ^ 20, 3 + 4im,
  true, NaN,
  5:8, println, Int8, stderr
]
  println(mio_obj, " is type of ", typeof(mio_obj))
end
hogehoge is type of String
@ is type of Char
紙 is type of Char
3 is type of Int64
-1 is type of Int64
2.88 is type of Float64
π is type of Irrational{:π}
exp is type of typeof(exp)
1//4 is type of Rational{Int64}
493 is type of UInt16
255 is type of UInt8
100000.0 is type of Float64
7766279631452241920 is type of Int64
100000000000000000000 is type of BigInt
3 + 4im is type of Complex{Int64}
true is type of Bool
NaN is type of Float64
5:8 is type of UnitRange{Int64}
println is type of typeof(println)
Int8 is type of DataType
 is type of Base.TTY

文字列操作

ここら辺はPython3とR言語両方の要素がある、ような気がする。

とりあえず。

mio_obj = "fugahoge"
println(mio_obj)
println("length $(length(mio_obj))")
println(mio_obj[1])         # index は 1 から始まる。これはR言語っぽい
println(mio_obj[2:6])       # スライシングができる。これは Python3 っぽい。Python3 なら mio_obj[1:6]
println(mio_obj[:5])        # Pythonista にとって思ったような挙動をしてくれないスライシング
                            # mio_obj[5:] とかもできない
println(mio_obj[end])       # 特殊な意味を持つ end。Python3 なら mio_obj[-1]
println(mio_obj[end-2:end]) # Python3 なら mio_obj[-2:]
println(mio_obj[1:2:5])     # Python3 なら mio_obj[0:5:2]

mio_obj = "あい上お柿クケコ"
println(mio_obj)
println("length $(length(mio_obj))")  # 文字列の長さはちゃんと取れるのに
println(mio_obj[(1*3)-2:(5*3)+1])     # Unicode 文字は mio_obj[1:2] のようにできない。うわめんどくさ。
mio_obj = "🇫🇯"
println("$(mio_obj[1]) $(mio_obj[5])") # Emoji はまた別の挙動をする
println("文字列の" * "足し算")             # 「.」でも「+」でもない

出力結果。

fugahoge
length 8
f
ugaho
h
e
oge
fgh
あい上お柿クケコ
length 8
あい上お柿ク
🇫 🇯

要は「Unicode文字を使った開発とかしないよね?」ということを言いたいらしい(探せばパッケージがあります)

using Pkg

Python3でいうところのpipimportを合わせたようなもの、R言語でいうところのrequirelibraryを合わせたようなものです。

とりあえず、インタープリター的なモードに移ります。ターミナルでjuliaを実行。

まあ、IJuliaでも入れてみましょう。

$ julia
...
julia> using Pkg
julia> Pkg.add("IJulia")   # 次回から不要
...
julia> using IJulia
julia> IJulia.notebook()

「パッケージの更新がされているかどうか確認したい!」とか「インストールしてあるかどうか分からない」とか困ったら、ほとんどこのQittaに書いてある。

Jupyter Notebookを使っても私はなんとも言いませんが、Jupyter Notebook自体はPython3で動作しているので、Juliaの恩恵が受けられるかどうかというのは保証しません。

REPL

IJuliaも説明しちゃったし、ついに書くか。

さっきからコード例とかでREPL、REPLと言っていたけど、「正直何?」と思った人はいると思います。

まずは先ほどの手順に従って、IJuliaを開いてみましょう。

下の画像の手順で進めましょう。

これで開けたと思います。

とりあえずHelloしましょう。

実行は「Shift+Return(Enter)」でできます。

ちゃんとできましたね。

では本題。

まずこう打ちます。

次に\piの末尾にカーソルを合わせた状態で「Tab」を押します。

……。

どうなりましたか?

こうなりました?

そうです、ギリシャ文字「π」が出力されたと思います。

これを実行してみるとこうなるはずです。

ちゃんと円周率になってる。

そもそもpiも実行できますが、xorは見栄えが悪くなるからそうもいかない。

これがREPLです。

「リプル」「レプル」「レップル」「アーリーピーエル」とかいろいろ発音されますが、まあ通じればなんでも良いと思います。

他にもいろいろ。ここを参考にした

辞書・タプル・テンソル

これはR言語やPythonのNumPyとPandasに近いと思う。

とりあえず、まずは配列、辞書、タプル、セットの宣言の方法。

arr = ["Uovo", "Casa", "Spaghettini"]
println("$arr $(typeof(arr))")
dic = Dict("une" => 1, "deux" => 2, "trois" => 3)
println("$dic $(typeof(dic))")
tup = (5, 0, 0, 7, 3, 1)
println("$tup $(typeof(tup))")
stt = Set(arr)
println("$stt $(typeof(stt))")

出力結果。

["Uovo", "Casa", "Spaghettini"] Array{String,1}
Dict("deux" => 2,"une" => 1,"trois" => 3) Dict{String,Int64}
(5, 0, 0, 7, 3, 1) NTuple{6,Int64}
Set(["Casa", "Uovo", "Spaghettini"]) Set{String}

次はテンソル、ベクトル(正確には要素が数値の配列)

matA = [1 2;
        4 3]
matB = [5 2;
        3 2]
# matA, matB は R^{2*2} の行列。宣言しやすすぎでは?
intC = 5
vecL = [2, 6]   # [] 内で「,」区切りにするとベクトルになる
# matL = [2 6]   # この場合は R^{1*2} の行列という扱い
println("matA +  matB = ", matA +  matB)
println("matA *  matB = ", matA *  matB)    # 行列の定義に沿うた掛け算。NumPy1.18 の「@」に相当 
println("matA .* matB = ", matA .* matB)    # 要素ごとの掛け算には「.*」を使う。NumPy1.18 の「*」に相当。「.」については次章を参考
println("matA .+ intC = ", matA .+ intC)    # 行列 + スカラー。これに関しても次章
println("matA *  vecL = ", matA * vecL)
println("matA'        = ", matA')           # 転置は「'」で表す。transpose(matA) とも

結果。

matA +  matB = [6 4; 7 5]
matA *  matB = [11 6; 29 14]
matA .* matB = [5 4; 12 6]
matA .+ intC = [6 7; 9 8]
matA *  vecL = [14, 26]
matA'        = [1 4; 2 3]

見栄えが良い……。

もともとJuliaは貪欲な人たちが作った言語で、MATLABのように数式から記述式が連想しやすいように設計されているため、こんなに行列やベクトルを宣言しやすいようになっています。

転置は「’」で表すんですね。解析系の人にとっては馴染みがないかもしれませんが、統計系の人にとっては馴染みがある記号だと思います。

零行列とか固有値分解とか、もっと有用な関数はありますが、そんなの調べれば出てくるでしょ。Qiitaではここに他のサイトではここにある

関数・コンストラクター・クラス

プログラム。

# 一般的な関数。Python3 でいうところの「def hoge_add(x, y):」
function hoge_add(x, y)
  x + y                 # Ruby のようにこう書いただけで return される。書いても良い
end # hoge_add




# Python3 でいうところのラムダ式みたいなやつとは言い難い
hoge_pseudo(x, y, z) = 2x + 3y + 4z   # 数式っぽくも書ける




# こっちがラムダ式
hoge_lambda = x -> x^2 + 2x - 1




# 戻り値はちゃんとタプルでしてくれる
function hoge_swap(a, b)
  b, a
end # hoge_swap




# 引数を動的にできる。Python3 でいうところの **kwargs
function hoge_dic(x; kwargs...)
  for arg in kwargs
    x += kwargs[arg.first]
  end
  return x
end




# 型指定。C/C++ では普通。この型以外ではエラーが出る。
function hoge_float_add(x::Float64, y::Float64)
  x + y
end




# 以下から main
# Python3 みたいに if __name__ == '__main__'みたいなものがないのか後で調べる
println("hoge_add(3, 5)          = ", hoge_add(3, 5))
println("hoge_pseudo(2, 4, 6)    = ", hoge_pseudo(2, 4, 6))
println("hoge_lambda(4)          = ", hoge_lambda(4))
println("hoge_swap(5, 6)         = ", hoge_swap(5, 6))
println("hoge_dic(12, kwargs...) = ", hoge_dic(12, uno=1, due=2, tre=3))
println("hoge_float_add(2., 3.)  = ", hoge_float_add(2., 3.))   # hoge_float_add(2, 3) では Int64 型なのでエラー
println("methods(hoge_float_add) = ", methods(hoge_float_add))  # これで引数の型は何にすれば良いかわかる 
matA = [1 2;
        4 3]
matB = [5 2;
        3 2]
intC = 5
println("sin.(matA)   = ",  sin.(matA))  # さっき作ったベクトルのブロードキャスト。「.」を使わないとエラーになる
println("matA ./ matB = ", matA ./ matB) # これで要素ごとの計算になる
println("matA .+ intC = ", matA .+ intC) # 行列 + スカラー はもちろんブロードキャストなのでこうなる




# そういえば演算子は関数なので、こんなわけのわからない書き方も罷り通る
println("+(6, 8, 19)             = ", +(6, 8, 19))   # 6 + 8 + 19 と同義

結果。

hoge_add(3, 5)          = 8
hoge_pseudo(2, 4, 6)    = 40
hoge_lambda(4)          = 23
hoge_swap(5, 6)         = (6, 5)
hoge_dic(12, kwargs...) = 18
hoge_float_add(2., 3.)  = 5.0
methods(hoge_float_add) = # 1 method for generic function "hoge_float_add":
[1] hoge_float_add(x::Float64, y::Float64) in Main at /Users/278mt/Desktop/julia-test/sample_07.jl:27
sin.(matA)   = [0.8414709848078965 0.9092974268256817; -0.7568024953079282 0.1411200080598672]
matA ./ matB = [0.2 1.0; 1.3333333333333333 1.5]
matA .+ intC = [6 7; 9 8]
+(6, 8, 19)             = 33

演算子は関数なので、上のようなわけのわからない書き方もできます。詳細は「演算子は関数なので」という部分のリンクを参照。

コンストラクターに関しては使うことになれば検索すれば良いと思うし、なんならここにある

私がみた感じでは、コンストラクターはSwift5のiotaみたい。

次にモジュール。他の言語で言うところのclass。

2年前の記事では、「Julia0.6.1はオブジェクト指向ではない」と書かれていました。今もオブジェクト志向とは言えない。カプセル化もインスタンスもPython3やC/C++みたいな実装がなされていないので、OOPではないのは確か。

オライリーのJuliaプログラミングクックブック――言語仕様からデータ分析、機械学習、数値計算までに従って、クラスのような実装は以下の方法でできるようです。

mutable struct Vector2D




  x::Float64
  y::Float64




end   # mutable struct Vector2D








function add(v1::Vector2D, v2::Vector2D)




  Vector2D(v1.x+v2.x, v1.y+v2.y)




end   # function add








# main
v1 = Vector2D(1, 2)
v2 = Vector2D(5, -7)




println(add(v1, v2))

でもオライリーさん、わざわざ0から始めるのは変だから、1から始めとくね。

で出力結果。

Vector2D(6.0, -5.0)

まあこれに関しては私も勉強不足だから、これからかな。

他には、RubyとSwift5を掛け合わせたみたいなdo構文があったり、TeXみたいにbegin endがあったり、Python3の__repr__みたいなletがあったりしますが、まあググってください。

try catch

省略。だって完全にここのままだもの

さいごに

もともとPython3は書けるので、Juliaの勉強にはそんなに苦労しませんでした。

というか、「そういえば機械学習にはJuliaって言語があったな」と思い出してからこのサイトに書くまでに3日も経ってない。

Python3やらR言語やらRubyやらFORTRANやらC/C++やらをやったことがある人にとっては結構楽に学べる言語だと思います。

もちろん、初心者大歓迎という参考書やら文献やらが増えれば、初心者にとってもやさしい言語なのではないでしょうか。

それでも私はPython3が好きだ()

後日談

ウェブ上に「クイックソートでJuliaとPython3を比較してみた!」みたいなのがあります。

正直言って、1回のクイックソートではJuliaのレゾンデートルを疑うようなプリコンパイルに遅さを感じると思います。

が、計算を1e5回試してみると、「あれ、Python3よりもJuliaの方が圧倒的に速くね?」となるので試してみてください(最初1e8回でやったけど、Python3のほうは現実的じゃないレベルの時間がかかった)

以下、比較のコード。型を指定すればもっと速くなる。

quicksort(xs) = quicksort!(copy(xs))
quicksort!(xs) = quicksort!(xs, 1, length(xs))

function quicksort!(xs, lo, hi)
  if lo < hi
    p = partition(xs, lo, hi)
    quicksort!(xs, lo, p - 1)
    quicksort!(xs, p + 1, hi)
  end
  return xs
end

function partition(xs, lo, hi)
  pivot = div(lo + hi, 2)
  pvalue = xs[pivot]
  xs[pivot], xs[hi] = xs[hi], xs[pivot]
  j = lo
  @inbounds for i in lo:hi-1
    if xs[i] <= pvalue
      xs[i], xs[j] = xs[j], xs[i]
      j += 1
    end
  end
  xs[j], xs[hi] = xs[hi], xs[j]
  return j
end


for i in 1:1e5
  res = quicksort([3, 6, 2, 4, 5, 1])
  if i % 1e4 == 0
    println(res)
  end
end
println("END")

Python3はこれ。

def quicksort(xs, lo=0, hi=5):

    if lo < hi:
        p = partition(xs, lo, hi)
        quicksort(xs, lo, p-1)
        quicksort(xs, p+1, hi)

    return xs


def partition(xs, lo, hi):

    pivot = (lo + hi) // 2
    pvalue = xs[pivot]
    xs[pivot], xs[hi] = xs[hi], xs[pivot]
    j = lo
    for i in range(lo, hi):
        if xs[i] <= pvalue:
            xs[i], xs[j] = xs[j], xs[i]
            j += 1

    xs[j], xs[hi] = xs[hi], xs[j]

    return j

for i in range(int(1e5)):
    res = quicksort([3, 6, 2, 4, 5, 1])
    if i % 1e4 == 0:
        print(res)

print('END')

まあ所感で言うと、Python3はawkやsedやbashの代わりにも使えるしスクレイピングも楽だしとりあえず何か思い立ったら選んで、大規模なデータを解析させたりするのにはJuliaを使うとかに、私の将来的にはなると思う。

ただOOPじゃないってのがちょっと私にとってはツラい。