読者です 読者をやめる 読者になる 読者になる

りんごがでている

何か役に立つことを書きます

Julia現状確認 (言語編)

julia

これはJuliaアドベントカレンダー1日目の記事です。2016年におけるJuliaの「現状」を確認していこうと思います。

Julia言語とは

Juliaとは一体どのような言語でしょうか。julialang.orgの最初の説明を用いると、「他の技術計算環境のユーザに馴染みのある構文を備えた、高レベル・高パフォーマンスな技術計算にための動的プログラミング言語」("a high-level, high-performance dynamic programming language for technical computing, with syntax that is familiar to users of other technical computing environments.")です。ここで言う「他の技術計算環境」とは、MATLABPythonのことを指していると考えられます。実際、Juliaの構文や関数名はMATLABとの類似性が高いため、Octaveのようなオープンソース版のMATLAB代替と捉えられることも多いようですが、プログラミング言語としてはかなり性質が異なります。また、Pythonとは文法や関数名の面ではそれほど類似性は無いように思いますが、似ている点も多数あります。

Juliaの最大の特徴を挙げるならば、動的プログラミング言語でありながらC言語などに匹敵するほど実行が高速であるという点でしょう。多くの技術計算では関数の呼び出しやループが頻繁に用いられますが、Juliaでは実行時コンパイル(JIT)のおかげでこれらの実行コストがネイティブコードにコンパイルするプログラミング言語並になっています。しかし、Juliaが行う実行時のコンパイルは、他の言語でよくみられるJITコンパイラとは性質を異にするようです。PyPy(PythonJITコンパイラ実装)やLuaJIT(LuaJITコンパイラ実装)はTracing JITと呼ばれるもので、実行時にプロファイルを行ってホットスポットを同定し、実行時の情報を集め、コンパイルと最適化を行います。しかし、JuliaのJITコンパイラはコードの実行前に型推論と最適化を行い、その後コードを実行します。型推論の結果、型が決定できる部分では強力な最適化を行い、曖昧性が残る部分では実行時の型に合わせて実行できるようにしています。

Julia言語の現状

Julia言語の実装であるJulia処理系はすべてGitHub上で開発されています。レポジトリはこちら( https://github.com/JuliaLang/julia)です。2016年12月1日現在の最新のリリース版はv0.5.0で、9月20日頃リリースされたものです。次期バージョンはv0.6でほぼ間違いないと思います。気になるのはv1.0のリリース時期ですが、今年のJuliaConでJuliaの生みの親のひとりであるStefan Karpinski氏が宣言したことによると、Julia 1.0のリリースは2017年を予定しているようです。次期バージョンのv0.6はv1.0のα版的な意味合いで開発されているようなので、v0.6が出てすぐv1.0がリリースされるものと思われます。しかし実は、v1.0が出てしばらく1.0のままということはなく、1-2年のうちにv2.0も出すつもりのようです。

振り返って、現行のv0.5ではどのような変更があったのかを確認してみます。まず性能面で大きな影響があったのが、クロージャが高速になったことです。v0.4までのJuliaでは、関数がすべて一つの型にまとめられており最適化の恩恵を受けにくかったのですが、v0.5からは関数が別々の型を持つようになりました。これによって、無名関数などの関数の呼び出しが高速化され、安心して使えるようになりました。例えば、map(x -> 2x, array)のようなコードがforループと同等の速度が出るようになります。また、ASCIIとUTF-8に分かれていた文字列型がString型に統一され、内部のエンコーディングを気にせず使えるようになりました。これで、Julia内では常にUTF-8エンコーディングのみを考慮すればよく、プログラムを書くのが大変楽になりました。さらに、新機能として、実験的にマルチスレッディングのサポートが入りました。今のところ、Julia内部でまだマルチスレッディングに対応していない部分(例えばIOなど)があるので、常に問題なく使えるわけではありませんが、競合などがない場合にはforループに@threadsをつけるだけで中身を並列に実行してくれます。これらの変更の他にも、generator式(i for i in 1:10などが式として受け渡しできる)やfusing構文(sin.(cos.(x))などがベクトルxに対して一時配列を作らない)などの新しい機能が導入されています。

では、v0.6はどうなるでしょうか。v0.6はv1.0のリリースのためにいつもより早めにfeature freezeが入るようですので、そろそろ新機能や変更点などが出揃うと思います。その中で、おそらく一番大きな変更が型システムの変更になるでしょう。実装はここ(https://github.com/JuliaLang/julia/pull/18457)にプルリクがあるので、すぐに試すことができます。これを少し詳しく説明してみます。

JuliaにはSetと呼ばれる集合を表す型があります。これは型パラメータを取ることができる型で型パラメータをTとすると、Set{T}と書けます。つまりSet{T}T型の要素を持つ集合です。このとき型パラメータTはどのような型でも良いのですが(注: 実行時にはhashを実装しているなどの制約がある)、逆にTに何らかの制約(T<:Integerなど)を付けた型は作ることができませんでした。しかし、次期バージョンのJuliaの型システムでは、Set{T} where T <: IntegerなどのようにT型に制約をつけることができます。以下の例では、型制約がない場合とある場合でどのようにサブタイピングの結果が異なるかを示しています。

julia> Set{T} where T  # 型制約なし
Set{T} where T

julia> Set{Int} <: (Set{T} where T)
true

julia> Set{Float64} <: (Set{T} where T)
true

julia> Set{T} where T <: Integer  # 型制約あり
Set{T} where T<:Integer

julia> Int <: Integer
true

julia> Set{Int} <: (Set{T} where T <: Integer)
true

julia> Float64 <: Integer
false

julia> Set{Float64} <: (Set{T} where T <: Integer)
false

サブタイピングに使えるということは、多重ディスパッチにも使えるということです。実際、以下の様な引数の型も書けます。

julia> f(xs::(Tuple{V,V} where V <: AbstractVector{T}), x::T) where T <: Integer = (xs[1] + x, xs[2] - x)
f (generic function with 1 method)

julia> f(([1,2,3], [1,2,3]), 2)
([3,4,5],[-1,0,1])

これが実際どのように役立つのかは、実例が無いので私にもまだよく分かっていません。また、記法に関してはまだ変更が入るかもしれません。

もうひとつ重要な新機能は、依存する関数の自動更新です。Julia v0.5では以下のように関数gを実行した後fを更新してもfの変更は反映されませんでした。

julia> f(x) = x + 1
f (generic function with 1 method)

julia> g(x) = f(2x)
g (generic function with 1 method)

julia> g(1)
3

julia> f(x) = x - 1
WARNING: Method definition f(Any) in module Main at REPL[1]:1 overwritten at REPL[4]:1.
f (generic function with 1 method)

julia> g(1)
3

これでは、開発中など頻繁に関数を書き換えるときにいちいち再起動する必要があります。Juliaはコードをコンパイルするためこのような動的な変更は実装不可能だと私は思っていましたが、現在進行中の変更(https://github.com/JuliaLang/julia/pull/17057)を取り入れると、fの変更が正しくgの実行に反映されるようになります。

julia> f(x) = x + 1
f (generic function with 1 method)

julia> g(x) = f(2x)
g (generic function with 1 method)

julia> g(1)
3

julia> f(x) = x - 1
WARNING: Method definition f(Any) in module Main at REPL[1]:1 overwritten at REPL[4]:1.
f (generic function with 2 methods)

julia> g(1)
1

大変不思議です。どうやらworldという利用可能なメソッドをチェックできるようにする機構が組み込まれているようなのですが、私には詳しく分かりません。もし何か分かりましたら、また記事にしようと思います。

この記事ではJulia言語の現状を簡単にまとめてみました。Julia言語以外のコミュニティの動向などは、他の機会にまたご紹介しようと思います。明日は、Ishitaさんの担当です。

勉強会が当日キャンセルされた話

注意
  • 2016年10月27日 15時 追記
  • 2016年11月2日 16時 タイトル変更

TECH_SALONという団体が主催しているfreee株式会社のJulia言語の勉強会に参加しようとしたら、当日の午後に突如勉強会の開催が取り消されました。ここがconnpassのイベントページです。

techsalon.connpass.com

Julia言語の勉強会ということで、どんな人が参加するのか楽しみにしていただけに大変残念です。 イベントページには、参加者に向けてわざわざ次のような注意事項が記載されているにも関わらず、主催者側が当日にキャンセルするというのは理解しがたいものがあります。

やむを得ない事情でキャンセルされる方は必ず3日前までにキャンセル申請をしてください。それ以降にキャンセルされる方は今後のTECH_SALONの勉強会の参加をお断りする可能性があります。

主催者側からどのような理由があって中止になったのかは明らかにされていませんが、以下の様なメッセージが届きました。

会場提供をしてくださっているfreee株式会社様のご都合により、急遽開催を見送らせて頂く事となりました。直前の連絡で大変申し訳ありません。次回、開催の際、ご連絡致しますので今後ともに何卒宜しくお願い致します。


(追記 2016年10月27日 15時)

主催団体より、中止事由の引用部分を次の内容に修正して欲しいとの連絡がありました。

勉強会の主催を務めるTECH_SALON側の判断により、

実際にはfreee株式会社の判断ではなく、TECH_SALONという主催団体側の判断とのことです。


会場となるfreee株式会社の都合によりキャンセルされたようですが、何があったのでしょうか。私は、連絡があったのを知らず直接会社まで行ったのですが、特に変わった様子は見受けられませんでした。

勉強会の講師の人に問い合わせると、以下の様な返信が届きました。

参加人数があまりにも少なかったため急遽開催を取りやめたようです。しかし、これは既に数日前に分かっていたことではないのでしょうか。

きっと、私には理解できない何か深遠な理由が裏に隠されているのだと思いますが、TECH_SALONの勉強会はこのような主催自体のドタキャンが発生する可能性があるため、参加の際にはご注意下さい。

Julia言語の0.5の変更点

9月20日にJulia言語の最新版である0.5がリリースされました。Juliaのメーリングリストに投稿されたアナウンスメントはこちらです: https://groups.google.com/d/msg/julia-users/J2DiH1GnM8o/aO2Ku8o-CgAJ

きっと近いうちに本家のブログで詳しい変更点の紹介があると思いますが、私のブログでも一足先に主要な変更点をご紹介しようと思います。

クロージャの効率化

とりわけ重要な変更点として挙げられるのがJuliaのクロージャが効率化されたことです。 0.4までのJuliaでは、Juliaの関数に関数を渡したりJuliaで関数を返すような関数を作ると、その実効速度が極めて遅いことが問題でした。 これは、すべての関数が Functionという型にまとめられていたせいで、Juliaのコンパイラが特化したコードを吐けないせいでした。 これが0.5では関数は各々自分専用の型を持つように変更されたため、クロージャを使ったコードでも遅くなるということが無くなりました。

関数を受け取る関数の例としてよく上げられるのがmap関数でしょう。 以下の簡単なベンチマークを見てもJulia 0.5では0.4に比べて10倍ほど速度が向上しています。

Julia 0.5.0:

julia> f = x -> 2x
(::#1) (generic function with 1 method)

julia> x = rand(100000);

julia> map(f, x);

julia> @time map(f, x);
  0.000572 seconds (134 allocations: 789.078 KB)

Julia 0.4.6:

julia> f = x -> 2x
(anonymous function)

julia> x = rand(100000);

julia> map(f, x);

jjulia> @time map(f, x);
  0.005628 seconds (200.01 k allocations: 3.815 MB)

クロージャを使うようなプログラムではこれでデザインの幅が広がり、今まで実行効率の観点からできなかったコードが書けるようになります。

ジェネレータ式

ジェネレータ式という新しい式が0.5から加わりました。 これは、イテレータをラップして式にしたようなもので、値を次々生成することができる式です。 簡単な例では、 2x for x in 1:9 のようなものが普通の値として扱えるようになりました。

julia> g = (2x for x in 1:9)
Base.Generator{UnitRange{Int64},##5#6}(#5,1:9)

julia> collect(g)
9-element Array{Int64,1}:
  2
  4
  6
  8
 10
 12
 14
 16
 18

もちろん関数はこのジェネレータを値として受け取ることもできるため、次のような計算もできます。

julia> sum(g)
90

また、ifで値をフィルターすることも可能です。

julia> h = (2x for x in 1:9 if isodd(x))
Base.Generator{Filter{Base.#isodd,UnitRange{Int64}},##7#8}(#7,Filter{Base.#isod
d,UnitRange{Int64}}(isodd,1:9))

julia> collect(h)
5-element Array{Int64,1}:
  2
  6
 10
 14
 18

julia> sum(h)
50

文字列型の統合

Julia 0.4の標準ライブラリに大量にあった文字列型が整理され、0.5では主にString型とSubString型のみになりました。 String型は従来のUTF8String型に当たるもので、ユニコード文字列を表現できます。SubString型は従来からありましたが、これはString型の一部分を切り出す際に使われています。 将来的にはSubString型もString型に統合される予定のようですが、0.5時点では0.4のASCIIStringUTF8StringString型に統合されたと見るのが良さそうです。

Julia 0.5.0:

julia> typeof("foo")
String

julia> typeof("いえい")
String

Julia 0.4.6:

julia> typeof("foo")
ASCIIString

julia> typeof("いえい")
UTF8String

昔の文字列型はLegacyStrings.jlパッケージに移動され、UTF-16などのエンコーディング方式はStringEncodings.jlパッケージで新たにサポートされるようです。

Fused broadcasting構文

Fused broadcasting構文は、Juliaのベクトル計算を効率化する新しい構文の拡張です。 0.4までのJuliaでは、配列に対して何度も関数適用を行うとその都度新しい配列が生成されていました。 新しいJuliaでは、これが構文レベルで融合(fuse)されるようになります。

sin.(cos.(x))という式があったとしましょう。 0.4ではまずcos.(x)が評価され、xの各要素にcos関数を適用した新しい配列が作られます。 続いてその各要素にsin関数が適用され、また新しい配列が作られ、先ほどの配列は将来的にGCにより破棄されます。 0.5では、この式はまず broadcast(x -> sin(cos(x)), x)のような式に変換されます。 これは、xから値を一つずつ取り出してcossinを順に適用するため、一時的な配列の生成が起きず、一気に2つの関数を適用した配列が生成されます。

さらに、x .= ...のような構文もbroadcast!(identity, x, ...)に変換されるので配列への代入がin-placeに行うことができるようになりました。

マルチスレッドのサポート

マルチスレッドを使った計算の並列化が新たにサポートされました。 今のところ、@threadsマクロを使ってfor文の並列化を行うことができます。 例えば、以下のように複数のタスクを並列に処理することができます。

using Base.Threads

function dotasks(tasks)
    @threads for i in 1:endof(tasks)
        dotask(tasks[i])
    end
end

配列などを領域で分割しておけば配列に対する処理の並列化もできますし、私の作ったBGZFStreams.jlではgzipの解凍処理をこのマルチスレッド機能を使って並列化しています。

テストフレームワークの強化

Julia標準のテストフレームワークがより強化され、テストをきれいに構造化できるようになりました。 今までBase.Testから提供されていた機能では複数のテストをまとめる機能がなく、フラットにすべてのテストを書き連ねるしか方法がありませんでした。 しかし0.5からは@testsetマクロが加わり、次のようにテストを構造化することができるようになりました。

@testset "sum" begin
    @test sum([1,2,3]) == 6
    @test sum([1.0, 2.0, 3.0]) == 6.0
end

その他の変更点

0.5では内部的なものも含め多数の変更がなされています。そのすべてはJuliaのリリースノートから参照することができますので、気になる方は是非調べてみて下さい。

JuliaからRを使う

先日のWACODE夏期講習でRCall.jlのデモをしたら、やはりウケが良かったようなので改めて紹介をします。

RCall.jlはJuliaからR言語の機能を呼び出すツールです。データの受け渡しからREPLでのインタラクティブな実行・プロットも簡単にできます。Juliaを使ってみたいけど、Rの豊富な資産を捨てる訳にはいかないといった方にはピッタリのライブラリです。

インストールは、Juliaの標準的な方法通り、julia -e 'Pkg.update(); Pkg.add(“RCall”)’を実行して下さい。これで最新版のRCall.jlがインストールされることになります。尚、次期Juliaのリリース候補v0.5-RC1では現在動かないようですが、リリースブランチでは直っているのでRC2では使えると思います。

簡単な演算で正しくインストールできたかを確認しましょう。JuliaのREPLを起動して、using RCallとしてRCall.jlを読み込み、R”1 + 1”と打ってR言語1 + 1を実行してみます。以下のように計算結果が表示されればOKです。 f:id:bicycle1885:20160809080928p:plain

R”…"という記法は、Juliaの非標準文字列と呼ばれる機能を利用したものです。この文字列の内側ではRのコードとして解釈され、Rの処理系がコードを実行してくれます。ダブルクウォートが含まれる場合はバックスラッシュでエスケープするか、R””” … “””とトリプルクウォートを使うこともできます。

R側の評価値を取り出す場合には、rcopyが使えます。

julia> rcopy(R"1 + 1")
2.0

julia> rcopy(R"1:5")
5-element Array{Int32,1}:
 1
 2
 3
 4
 5

julia> rcopy(R"""list(x=10, y="foo")""")
Dict{Symbol,Any} with 2 entries:
  :y => "foo"
  :x => 10.0

R側へJuliaの値を渡すには$で変数を埋め込みます。

julia> x = 1
1

julia> R"1 + $x"
RCall.RObject{RCall.RealSxp}
[1] 2

JuliaのDataFrames.jlパッケージが提供しているDataFrameなども自動的にRのdata.frameへと変換してくれます。

julia> using RDatasets

julia> iris = dataset("datasets", "iris");

julia> typeof(iris)
DataFrames.DataFrame

julia> R"head($iris)"
RCall.RObject{RCall.VecSxp}
  SepalLength SepalWidth PetalLength PetalWidth Species
1         5.1        3.5         1.4        0.2  setosa
2         4.9        3.0         1.4        0.2  setosa
3         4.7        3.2         1.3        0.2  setosa
4         4.6        3.1         1.5        0.2  setosa
5         5.0        3.6         1.4        0.2  setosa
6         5.4        3.9         1.7        0.4  setosa

プロットも簡単にできます。以下のようにすると、Juliaから渡したデータをRがプロットしてくれます。

julia> R"""pairs($iris[1:4], pch=21, bg=c("red", "green3", "blue")[unclass($iris$Species)])"""
RCall.RObject{RCall.NilSxp}
NULL

f:id:bicycle1885:20160809081146p:plain

いちいちR”…”で囲んだりが面倒なら、RのREPLに入りましょう。JuliaのREPLから$キーを押すと、RのREPLに入れます。Juliaに戻るにはバックスペースです。 f:id:bicycle1885:20160809081141p:plain

ここではRの文法が自由に使えるので、Rユーザーにとってはやりやすいでしょう。もちろん、Julia側からデータを受け取ることもR”…”の時と同じように可能です。

R> library(ggplot2)

R> ggplot($iris, aes(x=SepalLength, y=SepalWidth, color=Species)) + geom_point()

f:id:bicycle1885:20160809081246p:plain

Rのセッションではヘルプなども参照できますし補完も効きます。Juliaは気になるけどRから離れるのはチョット…という方は是非一度試してみてください。

『R言語徹底解説』(共立出版)をいただきました!

先日、出版社の方から新刊の『R言語徹底解説』(共立出版)をいただきました。ありがとうございます。

R言語徹底解説 / Hadley Wickham 著 石田 基広 市川 太祐 高柳 慎一 福島 真太朗 訳 | 共立出版

f:id:bicycle1885:20160211220632j:plain

この本は、R言語界では知らない人のいないHadley Wickham氏の書いた『Advanced R』の翻訳です。翻訳はこれまた日本のR言語界では知らない人のいないであろう石田先生・市川氏・高柳氏・福島氏の4名の手により行われています。本のページ数は500ページ以上もあり、R言語の仕様・プログラミングスタイル・高速化について書かれています。おそらく、Rの言語自体について書かれた日本語の本としては最も詳細な本でしょう。

プログラミング言語マニアとして見ると、R言語プログラミング言語としては非常に特異な言語だと思います。例えばスカラーにあたる型がなく、すべてベクトルとして扱ったり、引数の遅延評価など、他のメジャーな言語から来た人にとってはとても奇妙な振る舞いをします。また、GNU R (R言語の標準的な処理系) は決して高速とは言えず、実用されるプログラミング言語の中では最も遅い言語の部類でしょう。R言語はどういう動きをするのか、どうしてこうした特徴を持つのかについて答えてくれるのが、この『R言語徹底解説』です。

第6章「関数」では、R言語の関数について詳細に説明しています。6.3節の「すべての操作は関数呼び出しである」では、実はR言語でのオブジェクトに対する操作すべてが関数呼び出しであることを説明しています。ここでは、ifによる分岐やforによる反復などを含む本当にすべての操作が、関数呼び出しにより行われることを強調しています。普通のプログラミング言語では、if文の条件部分の評価のみが先に行われて、その値に応じて2つの別々に処理へと分岐します。関数呼び出しでif文を実現しようと思っても、関数に与えた引数が先に評価されるため、普通は実現できません。しかし、Rでは引数の遅延評価を行うことで、次のように実現可能になるわけです。

> i = 1
> `if`(i == 1, print("yes!"), print("no."))
[1] "yes!"

(6.3節から一部抜粋)

また、未定義の変数を関数に渡すこともできます。

> exists("x")
[1] FALSE
> f <- function(x) {}
> f(x)
NULL

このような機能があるおかげで、例えばggplot2ではggplot(diamonds, aes(x=carat, y=price)) + geom_point()のように簡潔に式が書けるわけです。他にも、この章で説明されている仮引数に対する値の束縛・クロージャ・コピー修正セマンティクスなども、奇妙なRという言語を扱う上で重要な知識でしょう。

第16章では、R言語のパフォーマンスの問題について議論されています。よく知られているようにGNU RによるR言語の実装は速くはありません。これは、R言語がデータ処理のやりやすさに特化した動的な言語であるためです。また、GNU Rの開発は、安定性のためにパフォーマンスを改善する変更に対してはかなり保守的なようです。これは開発において何を優先するかの問題で、決して間違った考えではないと思います。それに、多くの場合についてはR言語のコードを改善するだけでもそれなりにパフォーマンスは良くなります。そのため、第16章から18章のR言語でのパフォーマンスチューニングの手法を学べば十分でしょう。どうしても必要な場合にのみC言語C++を使うことになりますが、これらの言語をR言語から使う方法についても以降の章で説明されています。

個人的な使い方の話をすると、私はパフォーマンスが必要な部分はJulia言語を推しているのですが、R言語の安定感を求める人にはJulia言語はまだ時期尚早かもしれません。私の使い方としては、R言語でデータの基本的な処理 (バイオインフォなのでBioconductorなどのパッケージをよく使う) を行って簡単なテキストファイルを作り、それをもとにJulia言語で計算をして、R言語で可視化するというR > Julia > Rの順で使うことが多いです。なるべくJulia言語でできるところを増やしたいのですが、それでもR言語から離れられるほど成熟していません。

R言語徹底解説』はR言語の基礎を確認してストレスなく使うために役立つ本だと思います。R言語の仕様をあまり意識してこなかった人にとっては、全部は読まなくとも前半部分の第9章くらいまで目を通しておくだけでも随分違うと思います。

Juliaのユニットテスト

Julia Advent Calendar 9日目の記事です。なんとか途切れさせないでいきましょう。

Juliaと言えばユニットテストが書きやすい言語として有名です[要出典]。 何故書きやすいのかといえば、もちろんマクロがあるからです。 マクロのおかげで、よく分からないアサーションのコードをたくさん覚えなくて済みます。 この記事では、そのへんの理由や、Juliaでの最近のユニットテストの書き方について説明しようと思います。

Juliaのユニットテストの良いところ

以下のテストコードはsumという関数の動作をテストしている想定です:

julia> using Base.Test

# ok
julia> @test sum([1,2,3]) == 6

# not ok
julia> @test sum([1,2,3]) == 7
ERROR: test failed: 6 == 7
 in expression: sum([1,2,3]) == 7
 in error at /usr/local/julia/v0.4/lib/julia/sys.dylib
 in default_handler at test.jl:30
 in do_test at test.jl:53

# ok
julia> @test isa(sum([1,2,3]), Int)

# not ok
julia> @test isa(sum([1,2,3]), Float64)
ERROR: test failed: isa(sum([1,2,3]),Float64)
 in expression: isa(sum([1,2,3]),Float64)
 in error at /usr/local/julia/v0.4/lib/julia/sys.dylib
 in default_handler at test.jl:30
 in do_test at test.jl:53

ここで使われているのは@testというマクロです。 テストが失敗している方に注目していただきたのですが、エラーメッセージにtest failed: 6 == 7test failed: isa(sum([1,2,3]),Float64)のように、どの条件が失敗したのかが分かりやすく現れています。これは、@testマクロがテストの条件式をとり、その式自体を表示できるためです。特に、==などの二項演算子の場合は、左辺の値 sum([1,2,3]) の結果もエラーメッセージに表示してくれていることに気をつけてください。

マクロのない言語だと、テストするだけでもアサーションの関数が山盛りになってしまいます。 例えば、Pythonunittestだと、assertEqual(first, second, msg=None)assertIsInstance(obj, cls, msg=None)のような、ある条件を判定するアサーション関数がたくさん用意されています。ちょっとコレを全部覚えるのは辛いですし、全部assertTrue(expr, msg=None)を使うとテストがどう失敗したのかが分かりづらくなってしまいます。

モダンなユニットテストの方法

とは言え、@testマクロだけではちょっと機能が不足している面もあるでしょう。現在のリリース版v0.4系に付属しているBase.Testには、関係のあるテストをまとめる機能がありません。そのため、テストがフラットで把握しにくい構造になってしまいます。

次期バージョンのv0.5ではBase.Testが一新され、より構造化しやすくなっています。v0.5では@testsetというマクロが新たに追加され、テストをまとめることができるようになりました。 これは、@testset ["description"] begin ... endのように使い、テストの説明と複数のテストをまとめるブロックを取ることができるようになっています。さらに、テストセットの結果はまとめて報告されます:

julia> using Base.Test

julia> @testset "sum" begin
           @test sum([1,2,3]) == 6
           @test isa(sum([1,2,3]), Int)
           @test sum(1:3) == 6
       end
Test Summary: | Pass  Total
sum           |    3      3
Base.Test.DefaultTestSet("sum",Any[Test Passed
  Expression: sum([1,2,3]) == 6
   Evaluated: 6 == 6,Test Passed
  Expression: isa(sum([1,2,3]),Int),Test Passed
  Expression: sum(1:3) == 6
   Evaluated: 6 == 6],false)

実は、テスト結果のサマリーには色もつくようになりました。

f:id:bicycle1885:20151208184155p:plain

さらに、@testsetは以下のように何段でもネストできます:

@testset "basic functions" begin
    @testset "sum" begin
        @test ...
        @test ...
    end

    @testset "mean" begin
        @test ...
        @test ...
    end

    ...
end

@testsetがブロックとして取れるのはbegin ... endだけでなく、for ... endも取ることができます。 したがって、パラメータaを変えながらテストをしたい場合などは@testset for a in 1:10 ... endとすればよいでしょう。 実はこの機能はちょっと前まで@testloopとして提供されていたのですが、@testsetと統合してはどうかと提案したらすんなり受け入れられて現在はそのようになっています。Juliaってオープンですね(´ε` )

他にも、テストのレポートの仕方を変えるなど様々な拡張が可能になりました。 詳しい仕様は、v0.5のドキュメントを参照してください。

v0.5の便利なユニットテストを今使う

v0.5からは標準で@testsetが使えるようになるわけですが、やっぱり今v0.4で使いたいですね。 そのためには、BaseTestNext.jlを使いましょう。 以下のように書けば、v0.4向けのパッケージでも次世代のBase.Testを使うことができます。

if VERSION >= v"0.5-"
    using Base.Test
else
    using BaseTestNext
    const Test = BaseTestNext
end

Juliaの多分あまり知られてない関数達

Julia Advent Calendar 7日目の記事です。今週ひとりアドベントカレンダーの様相を呈してきているので誰か助けてください。

JuliaのBaseモジュールの関数はたくさんあるため、意外と知られていない関数もあると思います。 ですので、個人的によく使うなぁという関数をピックアップして紹介しようと思います。

開発

versioninfo()

Juliaのバージョンなどを環境を知るのに便利です。 バグ報告などの時はこの情報を必ず添付しましょう。 versioninfo(true)とするともっと詳しく出てきます。

julia> versioninfo()
Julia Version 0.4.1
Commit cbe1bee* (2015-11-08 10:33 UTC)
Platform Info:
  System: Darwin (x86_64-apple-darwin14.5.0)
  CPU: Intel(R) Core(TM) i5-4288U CPU @ 2.60GHz
  WORD_SIZE: 64
  BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell)
  LAPACK: libopenblas64_
  LIBM: libopenlibm
  LLVM: libLLVM-3.3

workspace()

REPLなどで環境をクリアにするのに使います。開発中などに一旦リセットする場合便利でしょう。

julia> x = 100
100

julia> workspace()

julia> x
ERROR: UndefVarError: x not defined

gc_enable()

GCの動作を管理できます。例えば、gc_enable(false)とすれば、GCを止めることができます。 ベンチマークなどのときに便利かもしれません。

数値計算

fld(x, y), cld(x, y)

fld(x, y)x / y して整数へ切り下げ、cld(x, y)x / yして整数へ切り上げる感じです。 浮動小数点数を経由せずに計算するので高速で正確です。

clamp(x, lo, hi)

xをある範囲 [lo, hi] に押し込む関数です。外れ値などを適当にあしらう(?)ときに便利です。

julia> clamp(-10, -1, 2)
-1

julia> clamp(1, -1, 2)
1

julia> clamp(10, -1, 2)
2

関数としては以下の図のような感じです。

f:id:bicycle1885:20151207011650p:plain

quantile(v, ps)

分位数を計算する関数です。何かの計算結果などで、ベクトルの分布を超大雑把に知るのに便利です。

julia> quantile(randn(1000), 0.5)
0.06427123889979461

julia> quantile(randn(1000), linspace(0, 1, 6))
6-element Array{Float64,1}:
 -3.40151
 -0.873353
 -0.265595
  0.215862
  0.776136
  3.97968

文字列

lpad(string, n, p)rpad(string, n, p)

文字stringを固定幅nで出力します。lpadは左側をpで埋め、rpadは右側をpで埋めます。 ちなみにpは省略可能で、省略すると空白文字になります。 アラインメントを取るような出力をするときに便利ですね。

julia> lpad("foo", 6)
"   foo"

julia> lpad("foobar", 6)
"foobar"

chomp(string)

文字列の最後にある改行を削除します。perlにも同名の関数がありますね。 ファイルを一行ずつ読み込んで処理するときに、改行文字を取り除くのに便利です。

for line in eachline(io)
    line = chomp(line)
    ....

その他

cat(dims, A...)

複数の配列A...をある次元dimsで結合します。

julia> cat(1, [1,2,3], [4, 5, 6])
6-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6

julia> cat(2, [1,2,3], [4, 5, 6])
3x2 Array{Int64,2}:
 1  4
 2  5
 3  6

なお、vcat(A....)hcat(A...)もあり、コレらはそれぞれcat(1, A...)cat(2, A...)と同じです。

mark(s)reset(s)

mark(s)でストリームsにマークを付け、reset(s)で前回マークをつけたところまでストリームを巻き戻す事ができます。 ファイルのパースなどで、先読みが必要な場合に便利ですね。ちなみにunmark(s)でマークは消せます。

xdump(x)

オブジェクトxのデータ構造をプリントします。 特に便利なのが式を扱う場合で、マクロを書くときには必須のツールになります。 例えば、import Foo.Bar: baz構文木がどうなってるかを知りたい場合は以下のように知れます。

julia> xdump(:(import Foo.Bar: baz))
Expr
  head: Symbol import
  args: Array(Any,(3,))
    1: Symbol Foo
    2: Symbol Bar
    3: Symbol baz
  typ: Any::DataType  <: Any

now()

文字通り、「今」を返します。「いまッ!」って感じです。

julia> now()
2015-12-07T17:20:31