X分で学ぶJulia
- 2016/11/20 追記: この記事は内容が一部古くなっています。最新版はこちらを参照して下さい
Julia Advent Calendar 2014 1日目の記事です。
まだまだJuliaは一般に知られていない言語ということもありまして、Juliaの基本的な機能を最短で学ぶ記事を初日に書いた次第です。 X<30くらいだと思います。
バージョンはJuliaの最新リリース版であるv0.3系を基にしていますが、特に異なる点は次期バージョンであるv0.4に関しても触れています。
Juliaの公式サイトは The Julia Language で 、Juliaのバイナリは Julia Downloads から入手できます。
文法
百聞は一見にしかず。まずはJuliaのコードをざっと見てみましょう。
function mandel(z) c = z maxiter = 80 for n = 1:maxiter if abs(z) > 2 return n-1 end z = z^2 + c end return maxiter end function randmatstat(t) n = 5 v = zeros(t) w = zeros(t) for i = 1:t a = randn(n,n) b = randn(n,n) c = randn(n,n) d = randn(n,n) P = [a b c d] Q = [a b; c d] v[i] = trace((P.'*P)^4) w[i] = trace((Q.'*Q)^4) end std(v)/mean(v), std(w)/mean(w) end
From: http://julialang.org/
どうでしょう?PythonやRubyをやったことがる人なら初見でも大体の意味が分かるのではないでしょうか?
関数定義・分岐・反復などの構文はそれぞれfunction ... end
, if ... end
, for ... end
, while ... end
のようにキーワードで始まりend
で終わります。
ちょうどRubyと同じような感じです。
インデントはPythonのように必要ではありませんが、4スペースでひとつのインデントを表すのが慣習です。
また、変数の宣言や型の指定は通常必要ありません。
こうしたコードはJuliaのLLVMベースのJITコンパイラによりコンパイルされ、C言語などで書いたコードとそれほど変わらない速度で実行できます。
変数
変数は特別に宣言せずとも初期化と同時に使用できます。
function
やfor
などほとんどのブロックは変数のスコープを新たに作ります。これはPythonなどと動作が異なりますので注意が必要です。例外的にスコープを作らないのはif ... end
とbegin ... end
です。
for i in 1:10 x = i end println(x) # ERROR: x not defined
if true x = 10 end println(x) # OUTPUT: 10
トップレベルで定義された変数はグローバル変数になります。
グローバル変数はコンパイラの最適化を難しくするため、const
をつけて定義すべきです。
数値型
Juliaの値は型を持ちます。Juliaでは動的に型がつき、様々な機能と密接に関わってきます。
整数型はsigned, unsignedと8bit, 16bit, 32bit, 64bit, 128bitの組み合わせとBool
型とBigInt
型で合計12種類あり、
それぞれsigned 64bitはInt64
やunsigned 32bitはUint32
など一貫性のある型名がつけられています(v0.4でUint
はUInt
に改名)。
浮動小数点の型も16bit, 32bit, 64bitとBigFloat
型で合計4種類があります。
BigInt
型とBigFloat
型はそれぞれ任意精度の整数と浮動小数点数です。
他には複素数のComplex{T}
型があり、T
という 型パラメータ(type parameter) で実部と虚部の数値の型を指定します。ちょうどHaskellの型変数(type variable)のようなものです。
科学計算のために作られているJuliaは、このように豊富な数値の方を持つ点が魅力のひとつです。
リテラル
大抵、何らかの値を作るリテラルは他の言語と同じです。
- 数値
- 文字(列)
- 文字
Char
型:'a'
,'樹'
- 文字列
ASCIIString
型:"deadbeef"
,"""Triple Quote String"""
- 文字
- その他
- 真偽値
Bool
型:true
,false
- シングルトン
Nothing
型:nothing
(v0.4ではVoid
型)
- 真偽値
Int
はInt32
またはInt64
のエイリアスです。
Juliaの対話実行環境(REPL)で確認してみましょう。
型はtypeof
関数で確認できます。
~/s/JuliaAdvent $ julia _ _ _ _(_)_ | A fresh approach to technical computing (_) | (_) (_) | Documentation: http://docs.julialang.org _ _ _| |_ __ _ | Type "help()" for help. | | | | | | |/ _` | | | | |_| | | | (_| | | Version 0.3.0 (2014-08-20 20:43 UTC) _/ |\__'_|_|_|\__'_| | |__/ | x86_64-apple-darwin13.3.0 julia> 42 42 julia> typeof(42) Int64 julia> typeof(1.0) Float64 julia> '漢' '漢' julia> typeof('漢') Char julia> typeof("deadbeef") ASCIIString (constructor with 2 methods) julia> typeof(true) Bool julia> typeof(nothing) Nothing (constructor with 1 method)
ベクトルは[]
で記述できます。
Juliaではインデックスは1始まりなので注意が必要です。
julia> [1,2,3] 3-element Array{Int64,1}: 1 2 3 julia> x = [1,2,3] 3-element Array{Int64,1}: 1 2 3 julia> x[1] 1
以降対話セッションは>
で表現します。
型
Juliaの値はひとつの 具体的な 型を持ちます。一部例外を除いて、型が自動的に別の型にキャストされることはありません
(一部例外とは、1 + 1.5
などの数値計算とコンストラクタです。 http://julia.readthedocs.org/en/latest/manual/conversion-and-promotion/)。
ここで、 具体的(concrete)な とわざわざ述べたのは、Juliaにはこれと対極をなす 抽象的(abstract)な 型があるためです。 適切な訳語がわからないため、ここでは原文通り具体的な型を「具体型」、抽象的な型を「抽象型」と表記します。
最も大きな違いは具体型はインスタンス化可能な一方で抽象型はインスタンス化ができない点です。
即ち、任意のx
に対して、typeof(x)
の結果は必ず具体型になります。
抽象型は具体型や他の抽象型のsupertypeとして機能し、型間のsubtype/supertypeの関係性は木構造のグラフをとります。
さらに、具体型は他の型のsupertypeにはなれませんので、必然的にグラフの葉が具体型に、内部ノードが抽象型となります。
このグラフの根にあるのがAny
という抽象型です。
Juliaでユーザーが型を定義するとデフォルトでこのAny
型のsubtypeになります。
(厳密には、None
という具体型を含む全ての型のsubtypeになっている抽象型があります。)
具体例で確認しましょう。Int64
とInt32
は共に具体型で、Integer
という抽象型のsubtypeになっています。
具体型はisleaftype
、subtype/supertypeの関係は<:
という関数で確認できます。
> isleaftype(Int64) true > isleaftype(Int32) true > isleaftype(Integer) false > Int64 <: Integer true > Int32 <: Integer true
自分で型を作るのも簡単です。
以下のようにtype
またはimmutable
に続けて型名を書き、フィールドを定義します。
type Person name::UTF8String age::Int end immutable Point x::Float64 y::Float64 end
デフォルトコンストラクタがありますので、即座にインスタンス化できます。
インスタンスのフィールドへは.
でアクセスできます。
person = Person("佐藤建太", 24) point = Point(1.0, 2.0) println("Hello, my name is $(person.name) and $(person.age) years old.")
関数/メソッド
Juliaの関数定義には二種類の書き方があります。
手続き型言語っぽい通常の関数定義と、関数型言語っぽい=
を用いた記法です。
# 手続き型風 (複数行のときに好まれる) function add_one(x) x + 1 end # 関数型風 (一行で書けるときに好まれる) add_one(x) = x + 1
返り値を表すreturn
はPerlやRubyのように省略可能です。
関数の引数は基本的に参照渡しのようになりますが、引数値の変数への束縛を変更できないため、immutableな型(例えばInt
やASCIIString
)の値は関数内で変更できません。
逆にmutableな型(例えばArray
などの配列)の値は関数内で変更することができます。
以下の例では標準のsort!
関数で破壊的に配列の中身をソートしています。
Juliaでは破壊的な関数の名前に!
をつけるのが慣習です。
> x = [4, 1, 2, 3] 4-element Array{Int64,1}: 4 1 2 3 > sort!(x) 4-element Array{Int64,1}: 1 2 3 4 > x 4-element Array{Int64,1}: 1 2 3 4
Juliaの他のスクリプト言語と違う大きな特徴のひとつが関数の 多重ディスパッチ(multiple dispatch) です。 これは、引数の型の組み合わせによって呼び出されるメソッドの実体が異なるという振る舞いを実現できます。
簡単な例で動作を確認しましょう。次の関数foo
は4つの異なる実装を持ちます。
function foo(x::Int) println("foo Int: $x") end function foo(x::Int, y::Int) println("foo Int Int: $x $y") end function foo(x::Float64, y::Float64) println("foo Float64 Float64: $x $y") end function foo(x::Int, y::Float64) println("foo Int Float64: $x $y") end
見ての通り、<仮引数>::<型>
の形で引数の型を指定できます。
これらの型の異なるメソッドは、呼び出し側の引数の型にマッチするものが実行されます。
もちろん、ユーザが定義した型に対しても同様に振る舞います。
以下のように型にマッチするものがない場合、その関数呼び出しはエラーになります。
> foo(1) foo Int: 1 > foo(1, 2) foo Int Int: 1 2 > foo(1.0, 2.0) foo Float64 Float64: 1.0 2.0 > foo(1, 2.0) foo Int Float64: 1 2.0 > foo(1.0, 2) ERROR: `foo` has no method matching foo(::Float64, ::Int64)
このように、Juliaではメソッドは関数に従属します。これは、メソッドがクラスに従属するクラスベースのプログラミング言語とは大きく異なります。
キーワード引数やオプショナル引数、可変長引数なども使えます。
=
の後に値を書くとオプショナルな引数として認識され、引数の区切りを,
の代わりに;
を使うとそれ以降の引数がキーワード引数として扱われます。
args...
とすると、仮引数args
は定義内ではタプルとなります。
# オプショナル引数 head(seq, n::Int=10) = seq[1:n] # キーワード引数 function optimize(func; iter=100, rate=0.1, alg=GradientDescent) # ... end # 可変長引数 sumof(args...) = sum(args)
- http://julia.readthedocs.org/en/latest/manual/functions/
- http://julia.readthedocs.org/en/latest/manual/methods/
構文糖衣
ここではJuliaで見られる特殊な構文をざっと見て行きます。
係数
変数の前に数値をつけると、暗黙的に積だと解釈されます。
これは多項式の記述や12px
など単位の記述に便利です。
> x = 2.1 2.1 > 2x 4.2 > 4x^2 + 3x + 2 25.94
範囲
配列から一部を取り出したり、ある範囲で反復すときなどに便利なのが範囲型です。
基本的な文法はstart:end
もしくはstart:step:end
のようにコロンで区切って記述します。
範囲自体もオブジェクトですので、変数に収めたり関数に渡したりもできます。
> 1:4 1:4 > for i in 1:4 println(i) end 1 2 3 4 > 1:2:10 1:2:10 > for i in 1:2:10 println(i) end 1 3 5 7 9
配列から一部を取り出すのにも範囲は使用されます。
終端はend
で指定できます。
> x = [1,2,3,4,5] 5-element Array{Int64,1}: 1 2 3 4 5 > x[1:3] 3-element Array{Int64,1}: 1 2 3 > x[4:end] 2-element Array{Int64,1}: 4 5 > x[2:end-2] 2-element Array{Int64,1}: 2 3
内包表記
配列や辞書はPythonのような内包表記で記述できます。
> [x for x in 1:4] 4-element Array{Int64,1}: 1 2 3 4 > [c => i for (c, i) in zip('a':'f', 1:6)] Dict{Char,Int64} with 6 entries: 'd' => 4 'f' => 6 'b' => 2 'e' => 5 'c' => 3 'a' => 1
ベクトル/行列
Juliaにおいてベクトルとは、一次元配列のことです。 ベクトルは以下のように記述します。
> [1, 2, 3] 3-element Array{Int64,1}: 1 2 3
,
を抜かすと、2次元の配列(行列)として解釈されます。
> [1 2 3] 1x3 Array{Int64,2}: 1 2 3
複数行の行列の記述は、以下のように;
で書けます。
> [1 2 3; 4 5 6] 2x3 Array{Int64,2}: 1 2 3 4 5 6
式の後に'
をつけると行列の転置になります。
> [1 2 3; 4 5 6]' 3x2 Array{Int64,2}: 1 4 2 5 3 6
無名関数
->
を使うことで、無名関数を記述できます。
> x -> 2x (anonymous function) > map(x -> 2x, [1,2,3]) 3-element Array{Int64,1}: 2 4 6
ブロック引数
第一引数に関数をとる関数は、位置を逆転させて代わりにdo ... end
ブロックを取ることができます。
即ち、以下の2つが同じ意味になります。
# 関数引数が先 map(x -> 2x, [1,2,3]) # 関数引数の代わりにブロックをとる map([1,2,3]) do x 2x end
これは、関数の中身が大きい場合に特に便利です。
さらに、ファイルを開いて自動的に閉じる処理の記述にも使われています。 以下のコードはファイルを開いて一行づつ処理をするパターンです。
open("somefile.txt") do f for line in eachline(f) # ... end end
非標準文字列
Juliaでは文字列の前に識別子をおくと、通常の文字列ではなくマクロとして解釈されます。 これを利用して、正規表現を記述できます。
> r"\w-\d \w+" r"\w-\d \w+" > match(r"\w-\d \w+", "B-2 Spirit") RegexMatch("B-2 Spirit")
他にはバージョン文字列の記述にも利用されています。
> v"1.2.3" v"1.2.3"
外部コマンド
バッククォートで囲うことでコマンドを記述できます。
作られたコマンドはrun
関数で実行できます。
> `ls -la` `ls -la` > typeof(`ls -la`) Cmd > run(`ls -la`) total 40 drwxr-xr-x+ 4 kenta staff 136 11 30 20:57 . drwxr-xr-x+ 26 kenta staff 884 11 29 17:17 .. -rw-r--r--+ 1 kenta staff 12692 11 30 20:57 learnjulia.md -rw-r--r--+ 1 kenta staff 234 11 29 18:10 sample.jl
便利なマクロ
@
マークで始まる式は、コンパイル時に別の式に展開されます。この機能を マクロ といいます。
別の言い方をすれば、通常の関数が実行時に値を取って値を返すのに対し、マクロとはコンパイル時に式を取って式を返す関数と言えます。
以下の例はアサーションに失敗すると例外を投げる@assert
マクロと、変数の状態をプリントする@show
マクロです。
これら2つはデバッグなどに大変役立ちます。
> x = 100 100 > @assert x < 10 ERROR: assertion failed: x < 10 in error at error.jl:21 > @show x x = 100 100
@printf
マクロは書式を指定したプリントのためのマクロです。
> @printf "line %3d: %s\n" 42 "foo" line 42: foo
ベンチマークなどに使えるのが実行時間とアロケートされたメモリを測る@time
マクロです。
最初の呼び出し時にはJITコンパイル分のオーバーヘッドがかかりますので注意が必要です。
> fib(n) = if n < 2 1 else fib(n-1) + fib(n-2) end fib (generic function with 1 method) > @time fib(10) elapsed time: 0.001739938 seconds (47552 bytes allocated) 89 > @time fib(20) elapsed time: 9.9135e-5 seconds (96 bytes allocated) 10946 > @time fib(40) elapsed time: 1.160873359 seconds (96 bytes allocated) 165580141
@code_llvm
と@code_native
は関数呼び出しで実行されるLLVMとネイティブコードをプリントします。
パフォーマンスが上がらない時などに原因を探るのに便利です。
> @code_llvm fib(10) define i64 @"julia_fib;42387"(i64) { top: %1 = icmp sgt i64 %0, 1, !dbg !1280 br i1 %1, label %L, label %if, !dbg !1280 if: ; preds = %top ret i64 1, !dbg !1282 L: ; preds = %top %2 = add i64 %0, -1, !dbg !1282 %3 = call i64 @"julia_fib;42374"(i64 %2), !dbg !1282 %4 = add i64 %0, -2, !dbg !1282 %5 = call i64 @"julia_fib;42374"(i64 %4), !dbg !1282 %6 = add i64 %5, %3, !dbg !1282 ret i64 %6, !dbg !1282 } > @code_native fib(10) .section __TEXT,__text,regular,pure_instructions Filename: none Source line: 1 push RBP mov RBP, RSP push R15 push R14 push RBX push RAX mov RBX, RDI cmp RBX, 1 jle L67 Source line: 1 lea RDI, QWORD PTR [RBX - 1] movabs R15, 4534292768 call R15 mov R14, RAX add RBX, -2 mov RDI, RBX call R15 add RAX, R14 L56: add RSP, 8 pop RBX pop R14 pop R15 pop RBP ret L67: mov EAX, 1 jmpq L56
マクロの書き方はちょっと入門の範囲を超えるので省略します。
パッケージ
JuliaのパッケージはGitレポジトリです。Julia本体もパッケージもGitHubを中心に回っています。 現在、Mercurialなど他のバージョン管理システムには対応していません。 しかし、パッケージ開発者を除くユーザとしてはGit自体を学ぶ必要はありません。 パッケージのインストールはJuliaの対話セッションから行うことができます。
> Pkg.add("DocOpt") INFO: Installing DocOpt v0.0.2 INFO: Package database updated
ほか、レポジトリのメタデータのアップデートはPkg.update()
で可能です。
公式に利用可能なパッケージはhttp://pkg.julialang.org/から一覧できます。
それ以外にもPkg.clone
を使うことで、公式レポジトリにはないがGitHubなどのGitレポジトリから直接インストールすることもできます。
その他
この記事では深く扱わなかった重要な部分については、Juliaのマニュアルへのリンクを張っておきます。 Juliaのマニュアルはとても読みやすく、それだけでプログラミング言語の勉強になるほど質の良いものです。
- 例外 http://julia.readthedocs.org/en/latest/manual/control-flow/#exception-handling
- モジュール http://julia.readthedocs.org/en/latest/manual/modules/
- 並列計算 http://julia.readthedocs.org/en/latest/manual/parallel-computing/
- C/Fortranの呼び出し http://julia.readthedocs.org/en/latest/manual/calling-c-and-fortran-code/
- コンストラクタ http://julia.readthedocs.org/en/latest/manual/constructors/
- 標準ライブラリ http://julia.readthedocs.org/en/latest/stdlib/base/