Julia v0.4.0-dev

Julia Advent Calendar 2014 の2日目*1の記事です。 前回の記事X 分で学ぶJulia -- りんごがでている では、Julia の最新リリースバージョンであるv0.3 の文法や特徴について、 ざっくりと、しかしながら仕事ができるぐらいには十分に解説がなされました。

この記事では、Julia の次期リリースに向けた開発バージョンである v0.4.0-dev について、v0.3 と大きく異なる点を中心に解説していきます。

開発バージョンについて

Julia は言語そのものの開発が活発であり、まだまだ仕様や文法が固まっていません。 しかしながら、ユーザを確保するためには、ある程度の期間にわたって仕様が変化しない安定バージョンが必要になります。 そのため、Julia には安定バージョン(最新リリースバージョン)と開発バージョンの2つが同時に存在します。 現在の安定バージョンはv0.3.3 (以下v0.3)で、開発バージョンはv0.4.0-dev (以下v0.4)です。

開発バージョンにはバグフィクスから性能向上、時には文法の変更まで、 大小様々な変更が日々なされていきます。 常々言われるように、Julia は非常に開発が活発であるため、開発バージョンは本当に毎日(毎時)変わります。 そのため開発バージョンはNightly build とも呼ばれます。

適当な時期になると、開発バージョンはバグフィクス以外の更新をストップして、 RC(Release Candidate: リリース候補)版へと移行します。 旧安定版を使っていたユーザは、RC 版が出ている間に、RC 版を実際に使うことで 次世代バージョンへと移行することが望まれます。 RC 版を出してしばらく様子を見て、特に問題がないようだと RC 版が次の安定リリース版になります。 なお、Github に登録されているマイルストーンによれば、 v0.4.0-RC は2015 年の2月10日頃に出る予定のようです*2

この記事は何?

RC 版が出てから次世代バージョンに移行するには数週間の期間が空きますが、 しつこいようですがJulia は言語自体の開発が非常に活発なので、 バージョン毎の変更点が結構大きなものとなっています。 実際に今回の開発バージョンをみるに、v0.3 とv0.4 との間とでは よく使われる文法レベルでもギャップが開いています。

この記事は、現在の開発版v0.4.0-devにおける最新リリース版v0.3.3 からの目立った変更点を列挙・概説することで、 現在v0.3.x を使っているだろう大多数のユーザが、 将来的に大きな混乱なく移行できることを目指します*3

面倒くさい人は「後方互換性を破る大きな変更」という項目と、 後は新機能の最初にある「ユーザ定義ドキュメント機能」だけ読めばいいかもしれません。

警告

(追記:2014-12-16

「開発版は毎日ビルドしてナンボだろJK」とか思っていたんですが、 ふと冷静に考えてみたら全くそんなことはないことに気づきました。 なので、適当なバージョンで固定して開発版をガンガン使っていくといいと思います(ぇー

本当にNightly Build する人には↓)

開発版を日常的にビルドして使っていると、ある日いきなり 今まで動いていたプログラムが動かなくなるといったことが往々にして起こります。 さらに、動くには動くけれど、警告が大量に表示されて可読性が著しく低下する、 と言った事態は日常茶飯事です。 むしろ今現在そんな状況です。 (20141217 追記: deprecated function を使うことで表示される警告を、Julia のコマンドラインオプションで抑制できるようになりました。 julia --depwarn={yes|no} で切り替え可能です。) 開発版は、このような正直いって面倒くさい状況をすら楽しめる人や、 文法の変更にかこつけてパッケージにプルリクエストを投げるのが 好きな人にしかおすすめしません。 特に、会社や組織にJulia を広げよう、と考えている人は、 絶対に 開発版 Nightly Buildを薦めてはいけないでしょう。

後方互換性を破る大きな変更

v0.4 では、後方互換性を破る変更がv0.3 の時以上にあるのですが、 その中でもかなり影響の広そうな破壊的変更が(私見では)3つあります。

です。これらは他の破壊的な変更点と比べても使っている事が多いので、 そのままv0.4-dev に移行するとまず確実に警告が出ます *4。 詳細に行く前に、v0.3 を使いながらでもできる準備があるので先に触れておきます。

Compat.jl パッケージ

Compat.jl パッケージを using すると、 開発版で使われている名前で古い版の型や変数を扱うことができます。 さらに @compat マクロを用いることで、 古い版からでも開発版で導入される新しい文法を使えるようになります *5

# Compat パッケージのインストール
julia-0.3> Pkg.update()
julia-0.3> Pkg.add("Compat")

# v0.4 ではString がAbstractString にリネームされる
# v0.3 では当然使えない
julia-0.3> AbstractString
ERROR: AbstractString not defined

# v0.4 における辞書型リテラル
# v0.3 では当然使えない
julia-0.3> Dict("Kagerou" => 1, "Shiranui" => 2) 
ERROR: unsupported or misplaced expression =>

# 魔法のパッケージ
julia-0.3> using Compat

# AbstractString がString の別名として定義される
julia-0.3> AbstractString
String

# v0.3 でも @compat を使うとv0.4 の辞書リテラルを使える
julia-0.3> @compat Dict("Kagerou" => 1, "Shiranui" => 2)
Dict{ASCIIString,Int64} with 2 entries:
  "Kagerou"  => 1
  "Shiranui" => 2

一度書き直す手間はかかりますが、 一旦書きなおしてしまえばそのまま移行することができます。 年末進行忙しいかもですが暇を見つけて是非やっておきましょう。

辞書型リテラルの変更

例えばアスキー文字列を整数に結びつける辞書型 Dict{ASCIIString, Int} を作るリテラルは、v0.3 までは

julia-0.3> (ASCIIString => Int)["Kagerou" => 1, "Shiranui" => 2]
Dict{ASCIIString,Int64} with 2 entries:
  "Kagerou"  => 1
  "Shiranui" => 2

のように配列リテラルの特別版のようなものになっていました。 この表記は自然なように思えますが、 =>予約語になってしまうという問題を抱えています。

そこで、v0.4 では、 まず有向グラフの辺を表現する型である Pair{A,B} 型が定義され、 =>Pair{A,B} コンストラクタの別名として オブジェクトとして定義されました。 そして、Dict{K,V}コンストラクタPair{K,V} な値を 可変長引数として渡すことで、辞書を作る事になりました。 言葉にすると面倒くさい感じがしますが、 実際には (K=>V)[]Dict{K,V}() と変えるだけです。

julia-0.4> Dict{ASCIIString, Int}( "Kagerou" => 1, "Shiranui" => 2 )
Dict{ASCIIString,Int64} with 2 entries:
  "Kagerou"  => 1
  "Shiranui" => 2

型推論があるので、大抵のケースでは辞書型の型パラメータは省略可能です。

julia-0.4> Dict( "Kagerou" => 1, "Shiranui" => 2 )
Dict{ASCIIString,Int64} with 2 entries:
  "Kagerou"  => 1
  "Shiranui" => 2

リスト内包表記は

julia-0.4> Dict{Int,Int}( [ x => x*x for x in 1:10] )
Dict{Int64,Int64} with 10 entries:
  7  => 49
  4  => 16
  9  => 81
  10 => 100
  2  => 4
  3  => 9
  5  => 25
  8  => 64
  6  => 36
  1  => 1

のように行います*6

Any 型配列リテラルの削除

v0.3 では、 [] の代わりに {} を用いて配列リテラルを書くことで 型推論をさせずに Any 型の配列、つまりなんでも入れられる配列を作ることが 出来ました。 しかし、同じことは型注釈を用いて Any[] とできるので、 この構文は冗長なものとなっています。 v0.4 ではこの構文がなくなることとなりました。

文字列抽象型 String の名前の変更

文字列を表す具体型 ASCIIStringUTF8String などは 抽象型 String の subtype でした。 この抽象型の名前が AbstractString に変更されます。

この変更により、 AbstractString が抽象型であることがはっきりし、 また将来的に具体型の名前として String を使えるようになりました。

新機能

ひとつでも気になるものがあったら開発版を触ってみましょう!(悪魔のささやき)

ユーザ定義ドキュメント機能

Julia ではhelp 関数に名前を渡したり、 REPL で? キーを叩いてヘルプモードに移行して調べたい名前を入力することで オンラインヘルプを見ることができます。

# REPL で'?' キーを押すとヘルプモードに移行する
help?> println
Base.println(x)

   Print (using "print()") "x" followed by a newline.

標準ライブラリの関数*7には大抵ドキュメントが付いているので 非常に便利なのですが、 各種パッケージを含むユーザ定義型では使えないため、歯痒い思いをすることが多くありました。

v0.4 では、そんなオンラインドキュメントにユーザが自分で追記をする機能が 追加されました。

# 最後のセミコロンは評価した値の表示の抑制
julia-0.4> const planck = 6.62606957e-34;

julia> @doc "Planck constant in SI Units" planck;

help?> planck
  Planck constant in SI Units

julia-0.4> @doc """
       function `hello` says "Hello, @doc!"
       """ hello() = println("Hello, @doc!")
hello (generic function with 1 method)

help?> hello
  function hello says "Hello, @doc!"

planck のように定義済みのオブジェクトにドキュメントを書くこともできるし、 hello のようにドキュメントを書くのと同時に定義することもできます。 ドキュメント文字列にはデフォルトでMarkdown 文法が使えます*8。 今回再現していないので伝わらないのですが、 バッククォートで囲った部分は違う色で表示されます。 また、今回のようにダブルクォーテーションを""" ... """ と3重にしておくと、 文字列中に出てくるダブルクォーテーションマークをエスケープしなくて良くなります。

ドキュメント文字列の次の行に関数や型をおく場合、 -> が必要です。 変数(定数)や関数にとどまらず、型にもドキュメントを書くことができます。

julia> @doc "
       type `Point` represents a point in 2D space
       " ->
       type Point
       x :: Float64
       y :: Float64
       end

help?> Point
  type Point represents a point in 2D space

Julia では同じ名前の関数を、別の型をもつ引数の組み合わせで定義することで 多重定義でき、それぞれの定義を引数の型に対応した「メソッド」と呼びます。 これらのメソッドにそれぞれ別のドキュメントを書くことができます。 ヘルプモードにおいて引数もしくは引数の型も同時に与えることで、 対応するメソッドのドキュメントを表示できます。

julia-0.4> @doc """
       `hello(x::Any)` greets `x`
       """ hello(x::Any) = println("Hello, $(x)!")
hello (generic function with 2 methods)

help?> hello
  hello() says "Hello, @doc!"

  hello(x::Any) greets the argument x

help?> hello()
  hello() says "Hello, @doc!"

help?> hello(Any)
  hello(x::Any) greets x

help?> hello(1)
  hello(x::Any) greets x

ちなみに、ドキュメント文字列を書いている場所には、 文字列以外の任意のオブジェクトが置けます。 また、@doc マクロはドキュメントを定義するだけでなく、 定義したドキュメントを取り出すこともできます。

julia> @doc 42 answer() = 0
answer (generic function with 1 method)

julia> @doc answer
1-element Array{Int64,1}:
 42

help?> answer
1-element Array{Int64,1}:
 42

お行儀は良くないけれど、何か面白いことができるかもしれませんね。

なお、書法などはまだまだ変更する可能性があるようです。 詳しくはREPL で実際に@doc のヘルプを見てください。 ちなみに @doc のヘルプは実際に @doc を用いて書かれているので、 base/docs.jl を読むと更に学べるでしょう。

関数呼び出し演算子 () をユーザ定義型で使えるようになった

a(x...) とかくと自動的にcall(a,x...) となるようになりました。 つまり、ユーザ定義型 A について、Base.call(a::A, x) を 定義することで、ユーザ定義型についても 関数呼び出し構文が使えるようになりました。

今まで関数の引数を部分適用したり高階関数を書くときは、 ラムダ式を使って無名関数を用いていました。 概念的にはこれでも全く問題ないのですが、 無名関数にはJIT コンパイルが働かないために、 性能の面からは使いづらいものとなっていました。 この新機能を用いると、例えば関数の部分適用は、 元の関数と束縛する仮引数の番号と束縛する実引数との3つ組を保存して、 call 関数のメソッドとして部分適用して得られた関数の適用を行うものを 実装することで実現できます。

キーワード引数が使いやすくなった

関数にキーワード引数を渡す方法は従来では次のとおりでした。

julia> foo(; x=0, y=0, z=0) = 100x + 10y + z

# 普通の呼び方
julia> foo(; x=1, y=2, z=3)
123

# 辞書のアンパック代入
julia> params = Dict( :x => 1, :y => 2, :z => 3) ;

julia> foo(; params...)
123

キーワード引数の名前を動的に変えたい場合(変数で渡したい場合)は、 次のように変数名シンボルと値のタプルを配列に入れてアンパック代入する必要がありました。

julia> a, b, c = :x, :y, :z ;

# (Symbol, Any) 配列のアンパック代入 ...
julia> foo(; [(a,1), (b,2), (c,3)]...)
123

v0.4 からは次のように簡便にかけます。

# 新記法
julia-0.4> foo(; a => 1, (b, 2), [c, 3] )
12

最初の a の例は、実際にはPair{Symbol,Int} を作っていることになります。 他にも普通使うだろう記法としてタプルと配列を例示しましたが、 実際にはイテレーティブなオブジェクト、 つまり for x in xsxs の位置にかけるオブジェクトならなんでもかけます。 この時、最初の2つの要素が名前と値として使われます。

@inline マクロの導入

まず、関数にコンパイラ向けのメタデータを付けられるようになりました。 詳細はよくわかっていませんが、何もしない場合は特に何も付けないようなので、 従来の使い方でのオーバーヘッドはないようです。たぶん。

さて、@inline マクロを関数定義の頭につけることで、 その関数にインライン化用のメタデータをつけられます。 インライン関数 inner を中で呼ぶ関数 outerJIT コンパイルされるとき、 inner の関数本体が outer の該当箇所にベタ書きされます。 これにより、速度が向上する場合があります。

注意としては、外側の関数がコンパイルされて初めて効果が出るということです。 そのため、コンパイルされていない最初の outer の呼び出しや、 トップレベルからの inner の呼び出しではインライン化されずに 普通に inner が関数として呼び出されます。 この時、(多分メタデータの解釈にオーバーヘッドがかかるために) 普通の関数を呼び出すよりも性能が低下するようです。 使い方の例と、実行速度の測定結果は以下のgist を参照ください。 使い方と結果

型パラメータに任意のisbits オブジェクトを取れるようになった

isbits な型とは、変更不可能で、 他の非isbits なオブジェクトへの参照をメンバに持たない、 真に変更不可能なオブジェクトです。 例えば UInt8Float64 等の数値型や、 これらだけを含む immutable 型である Complex{Float64} などが該当します *9

v0.3 までは型パラメータとして通常の型の他に、Int 型や Bool 型の値、 つまり整数 1 や真偽値 true など、を指定出来ました。 例えば配列型 Array{T,N}T は要素の型を、N は配列の添字の数*10を表します。

v0.4 では型パラメータとして他のisbits 型の値、例えば浮動小数点数 1.38e-23 や 分数 1 // 2 などが指定できるようになります。 これにより型の表現力が強化されます。 例えば、科学技術計算における次元解析を行う SIUnits.jl というパッケージが 将来的に拡張されるはずですし、 SI 単位系以外への変換も行えるようになるかもしれません。

staged function の導入

文法にはまだまだ考慮の余地があるようですが、 staged function を導入する予定のようです。 staged function については私は専門外でよくわからないのですが、 ひじょーーーーーーに大雑把にいえば、 複数引数(パラメータ)を取る関数に関して、 広範囲、例えばプログラム全体、にわたって変わらないパラメータを固定して、 残りの引数に関する関数を静的に作る技術、のようです。 staged function をうまいこと導入することによって メタプログラミングDSL などがより効率的に書けるようになるということです。

正直いって良くわかっていないので、是非ともプログラミング言語の専門家の方(もしくはそれに準じる方)に、 Julia を用いて具体的解説していただきたいところです。

後方互換性を破る小さな変更

最初に述べた3つに比べれば影響は小さいと思います。

符号なし整数型のスペル変更

符号なし整数型は今まで UintUint64 のように書かれましたが、 v0.4 ではUIntUInt64 といった具合に、大文字の I を使うようになりました。

なお、AppleSwift 言語における符号なし整数型を見て このスペルの合理性 (U(nsigned) I(nt))に気づいたそうですw

NothingNone の名前変更

Nothing

Julia の関数では、明示的に return を書かずとも最後に評価した式の値が返り値となります。 基本的に関数というのは、引数を元にしてなにかを返すものですが、 時には副作用だけを期待して何も返さない関数、というのを書きたくなることがあります。 呼び出し元に、でてきた結果をそのまま捨ててもらっても良いのですが、 返り値には何の意味もないのだということを明示したほうがユーザには優しいでしょう *11 *12。 そんな時に使えるのが nothing です。その型は Nothing です。

None

抽象型を使うことでいくつかの具体型をひとまとめにすることができますが、 既存の具体型に外から抽象型を与えることはできません。 その時に使うのが Union です。

julia> Int64 <: Integer
true

julia> Int64 <: Union(Int64, ASCIIString)
true

julia> Union( Union(Int64, UInt64), ASCIIString)
Union(ASCIIString, Int64, UInt64)

Union はこのように型の「足し算」となっているのですが、 その零元が None です。

Nullable

さて、「無」に関連づいた型が既に2つあるわけですが、 v0.4 では新たに無効な値になりうる値の型として、 Nullable{T} が導入されました。

julia-0.4> invaid = Nullable{Int}()
Nullable{Int64}()

julia-0.4> isnull(invalid)
true

julia-0.4> get(invalid)
ERROR: NullException()
 in get at nullable.jl:26

julia-0.4> get(invalid, 137)
137

julia-0.4> valid = Nullable{Int}(42)
Nullable(42)

julia-0.4> isnull(valid)
false

julia-0.4> get(valid)
42

julia-0.4> get(valid, 137)
42

「無」多すぎ問題

こうして似たような名前が増えてきたので、 NothingVoid に、NoneUnion() に変わりました *13

余談ですがもう1つ重要な「無」があって、それは () です。 主に0-引数関数 の引数を表すのに使います。これ自体の型も () です。

CharInteger ではなくなった

今までは 文字型 CharInteger のsubtype だったので、 加減や比較などのやりたい演算の枠を超えてなんでも *14 できる状況でした。

v0.4 では Char が直接 Any のsubtype になり、 文字コード操作のための比較や加減など、限られた演算のみがサポートされます。

他の変更と比べると、普通のバグフィックスですね。

convert で数値型を変更するときにオーバーフローチェックをするようになった

数値型をより狭い数値型に変換するとき、元の値によってはあふれることがあります。 今までは無視していたのですが、v0.4 では例外を投げるようになりました。

その他 deprecated な関数達

base/deprecated.jl に大量にならんだ関数のうち、 # 0.3 deprecations# 0.4 deprecations の間にあるものはv0.4.0rc ですべて消されます。 これらの関数を呼ぶと警告が出るので、さっさと直しましょう:D

機能改善

線形代数や乱数を中心として、様々な機能改善*15 がなされています。 たくさんあるので全部は挙げきれないで、個人的に気になったもののみ。

ガウス乱数生成関数 randn の高速化

速度が5割増しから2倍近くぐらいまで向上しました。 乱数生成はモンテカルラーにとっては最適化の律速になりうるので、 非常に喜ばしい改善です。

結構単純な変更で劇的な速度向上をしているので、 後日別の記事にて改めて触れたいと思います。 (20141220 : 触れました。http://yomichi.hateblo.jp/entry/2014/12/20/020748

Unicode7 のサポート

文字種がふえるよ!やったn(ry

最後に

どれか1つでも「これは!」という項目があれば、 実際に最新開発版を手元において実行してみることをおすすめします。 最新安定バージョンとの共存は、 きっと次の記事にてchezou さんが解説してくださるはずです *16。 開発版は本当に毎日、それもドキュメントなどの更新も含めて何回も更新されるので、 何が変わったのかわかりづらいかと思いますが、 基本的にはNEWS.md だけを追っていれば問題無いと思います。

*1:まだ27:40 ですよ!!!

*2:v0.3.0-RC は思いっきりオーバーランしましたが……

*3:私自身の備忘録という面もあります。

*4:v0.4.0rc までは警告を出しつつよしなにしてくれるが、rc 版では容赦なく落としてくると思われる。

*5:新文法を旧文法に変換することで実現している

*6:今までのやり方でも警告なしで動くが、仕様かどうかは不明

*7:型名 = コンストラクタも含む

*8:今後増やすのかどうかは不明

*9:isbits 関数のヘルプより。

*10:テンソルの階数

*11:実装を変更することで返り値が変わったりするので

*12:また、ある関数を同じ型で呼んだ時には、常に同じ型の値が返るようにしたほうがパフォーマンスが向上する

*13:None はもともと Union() の別名だったが、それをやめた形になる。

*14: 指数関数に入れたり、文字型の複素数とか有理数を作ったりもできる。 ちなみに 'a' // 'b' とかやるとスタックオーバーフローで落ちるw

*15:主に速度向上

*16:念のためさらっとコマンドだけ述べておくと、 git checkout v0.3.3 した後に make cleanall && make install して、作られたディレクトリ直下の bin/julia にパスを通せばよい。