cv.cm

コーディング · プログラミング言語

Julia入門:初心者向けチュートリアル

約10分で読めます

結論

Juliaは、高速な数値計算・科学技術計算のために設計された、高水準の動的型付け言語です。PythonやMATLABのように読めますが、各関数は最初に呼ばれたときにネイティブコードへコンパイルされるため、ループがCに近い速度で動きます。中心となる考え方は多重ディスパッチで、関数は、すべての引数の型に基づいてメソッドを選びます。juliaupでインストールし、julia hello.jlでファイルを実行します。

Juliaとは何か、誰が使うのか

Juliaは、Jeff Bezanson、Stefan Karpinski、Viral Shah、Alan Edelmanが作り、2012年に公開されました。バージョン1.0は2018年に登場し、1.xシリーズは後方互換性を保っています。Juliaは「2言語問題」、つまり遅くて簡単な言語でプロトタイプを作り、その後で処理の重い部分をCやFortranで書き直すという問題の解決を目指しています。

Juliaは、科学者、エンジニア、データアナリストが、シミュレーション、微分方程式、最適化、統計、機械学習、ハイパフォーマンスコンピューティングに使っています。パッケージのエコシステムは、データフレーム、作図、GPU計算、自動微分をカバーしています。

Juliaのインストールとhello, worldの実行

おすすめのインストーラーはjuliaupです。Windows、macOS、LinuxでJuliaのバージョンをインストール・更新します(julialang.org/downloadsを参照)。インストール後は、juliaでREPLが起動し、julia file.jlでスクリプトが実行されます。

# hello.jl
println("Hello, world!")
julia hello.jl
# Hello, world!
julia
# julia> 1 + 2
# 3

REPLでは、]でパッケージモードに入り(たとえばadd DataFrames)、?で名前のヘルプ、;でシェルを使えます。Backspaceを押すと通常のプロンプトに戻ります。

変数と基本の型

変数の宣言は不要ですが、どの値にも具体的な型があり、typeofで調べられます。必要なら、関数の引数や構造体のフィールドに型注釈を付けられます。

型例補足
Int644264ビットシステムでのデフォルトの整数。オーバーフローすると巻き戻ります
Float643.14, 1e-3デフォルトの浮動小数点数
Rational1//3正確な分数
String"hello"UTF-8。"$name"で式を埋め込めます
Char'a'Unicodeの1文字
Booltrue, false論理値
Symbol:nameインターンされた名前。キーやオプションによく使います
Nothingnothing「値なし」。結果を持たない関数が返します
name = "Ana"
age = 30
println("$name is $age, next year $(age + 1)")
# Ana is 30, next year 31
typeof(age)   # Int64
7 / 2         # 3.5  (always a float)
7 ÷ 2         # 3    (integer division, also div(7, 2))
2^10          # 1024

JuliaはπやαのようなUnicodeの名前を受け付けます。REPLやほとんどのエディターでは、\piと入力してTabを押すとπになります。

制御構文

ブロックはキーワードendで閉じます。インデントは読みやすさのためだけにあります。if、for、whileはほとんどの言語と同じように動き、内包表記で配列を1行で作れます。

function sign_name(n)
    if n > 0
        "positive"
    elseif n < 0
        "negative"
    else
        "zero"
    end
end
for i in 1:3
    println("$i squared is $(i^2)")
end
squares = [x^2 for x in 1:5 if isodd(x)]   # [1, 9, 25]
x = 5
result = x > 3 ? "big" : "small"           # "big"

1:3は両端を含む範囲です。関数やifブロックの最後の式がその値になるので、sign_name(-2)は明示的なreturnなしで"negative"を返します。

関数

add(x, y) = x + y                 # one-line definition
function greet(name = "world"; punctuation = "!")
    return "Hello, $name$punctuation"
end
greet()                           # "Hello, world!"
greet("Ana", punctuation = ".")   # "Hello, Ana."
square = x -> x^2                 # anonymous function
map(square, [1, 2, 3])            # [1, 4, 9]
v = [3, 1, 2]
sort!(v)                          # v is now [1, 2, 3]

位置引数はセミコロンの前、キーワード引数はセミコロンの後に書きます。慣例として、sort!やpush!のように名前が!で終わる関数は引数の1つを変更し、sortは新しい配列を返します。

配列・タプル・辞書

構造リテラル補足
Vector[1, 2, 3]1始まりで変更可能。push!で伸ばせます
Matrix[1 2; 3 4]空白で列を、;で行を区切ります
Tuple(1, "a")イミュータブルで、長さは固定
Named tuple(name = "Ana", age = 30)フィールドには.nameでアクセスします
DictDict("a" => 1)変更可能なハッシュマップ
SetSet([1, 2, 2])重複のない値
v = [10, 20, 30]
v[1]          # 10   (first element)
v[end]        # 30
push!(v, 40)
A = [1 2; 3 4]
A * A         # matrix product: [7 10; 15 22]
A .* A        # element-wise:   [1 4; 9 16]
v .+ 1        # broadcasting:   [11, 21, 31, 41]
sqrt.([1, 4, 9])   # [1.0, 2.0, 3.0]

ドット構文はブロードキャストと呼ばれます。f.(x)はfをすべての要素に適用し、.+や.*は要素ごとに計算します。Juliaは、ドット付きの演算の連なりを1つのループにまとめます。

特徴的な機能:多重ディスパッチ

Juliaの関数は、多くのメソッドを持てます。呼び出すと、Juliaはすべての引数の型に対して最も具体的なメソッドを選びます。これはクラスのメソッドに代わるものです。型はデータを保持し、振る舞いは型に対するメソッドを定義して加えます。別のパッケージからでもそれができます。

abstract type Shape end
struct Circle <: Shape
    r::Float64
end
struct Rect <: Shape
    w::Float64
    h::Float64
end
area(c::Circle) = π * c.r^2
area(r::Rect) = r.w * r.h
shapes = [Circle(1.0), Rect(2.0, 3.0)]
println(round.(area.(shapes), digits = 2))   # [3.14, 6.0]

Juliaは、見た引数の型に特化した関数のバージョンをコンパイルするため、関数の最初の呼び出しは(コンパイルを含むので)遅く、以降の呼び出しは高速です。作成後にフィールドを変更する必要がある場合は、mutable structを使います。

エラー処理

Juliaは例外を使います。ArgumentErrorやDomainErrorなどの組み込みの例外型を投げるか、Exceptionのサブタイプとなる構造体として独自のものを定義します。tryparseは、例外を投げずにnothingを返します。

function parse_age(s)
    n = tryparse(Int, s)
    n === nothing && throw(ArgumentError("not a number: $s"))
    n < 0 && throw(DomainError(n, "age must not be negative"))
    return n
end
try
    parse_age("abc")
catch e
    if e isa ArgumentError
        println("Error: ", e.msg)
    else
        rethrow()
    end
end
# Error: not a number: abc

小さな完成プログラム:単語の出現頻度

text = "the cat and the hat and the bat"
counts = Dict{String,Int}()
for w in split(text)
    counts[w] = get(counts, w, 0) + 1
end
top = sort(collect(counts), by = last, rev = true)
for (word, n) in first(top, 2)
    println(word, " ", n)
end
# the 3
# and 2

splitは文字列を空白で分割し、getは新しい単語に対してデフォルトの0を返し、collectは辞書をword => countのペアのベクターに変え、by = lastは回数で並べ替えます。

次に学ぶこと

  • docs.julialang.orgの公式Juliaマニュアル。「Getting Started」から始めます。
  • julialang.orgの学習ページ。チュートリアル、書籍、講座が並んでいます。
  • コードを最適化する前に、マニュアルの「Performance Tips」の章を読む。
  • DataFrames.jl、Plots.jl、DifferentialEquations.jlなどのパッケージを見てみる。

よくある質問

JuliaはPythonより速いですか?

ループや数値計算の多いコードでは、ネイティブコードにコンパイルされるため、Juliaは通常、純粋なPythonよりずっと速くなります。NumPyなどのライブラリを使ったPythonは、ベクトル化された演算なら高速になりえるので、差は処理内容によります。

Juliaはなぜ1から数え始めるのですか?

Juliaは、数学やMATLAB、R、Fortranなどの言語の慣例に従っており、配列の最初の要素の添字は1です。

Juliaのコードの初回実行が遅いのはなぜですか?

Juliaは、各関数を最初に呼ばれたときに、その引数の型に合わせてコンパイルします。以降の呼び出しはコンパイル済みのコードを再利用するので、性能はウォームアップの呼び出しの後か、ベンチマーク用のパッケージで計測してください。

Juliaは無料ですか?

はい。JuliaはMITライセンスのオープンソースで、ほぼすべてのパッケージもオープンソースです。

コーディング講座の一覧