『R言語徹底解説』(共立出版)をいただきました!
先日、出版社の方から新刊の『R言語徹底解説』(共立出版)をいただきました。ありがとうございます。
R言語徹底解説 / Hadley Wickham 著 石田 基広 市川 太祐 高柳 慎一 福島 真太朗 訳 | 共立出版
この本は、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 == 7
やtest failed: isa(sum([1,2,3]),Float64)
のように、どの条件が失敗したのかが分かりやすく現れています。これは、@test
マクロがテストの条件式をとり、その式自体を表示できるためです。特に、==
などの二項演算子の場合は、左辺の値 sum([1,2,3])
の結果もエラーメッセージに表示してくれていることに気をつけてください。
マクロのない言語だと、テストするだけでもアサーションの関数が山盛りになってしまいます。
例えば、Pythonのunittestだと、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)
実は、テスト結果のサマリーには色もつくようになりました。
さらに、@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
関数としては以下の図のような感じです。
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
Japan.R 2015でJuliaの布教してきました
Julia Advent Calendar 6日目の記事です。
昨日のJapan.R 2015のLTでJuliaの布教をしてきました。 5分で55枚のスライドをやり切り、時間ピッタリで無事終えました。
Juliaの速さを早口で表現 #japanR
— さわみる (@sourmilk) 2015, 12月 5
だそうです。
当日の資料はSlideShareにあげてあります:
言いたいことは3点で、
- Juliaは速い
- コミュニティもパッケージも育ってきている
- 最近はJuliaを使っている会社や組織も増えてきた
ということです。 もちろん、これからもJuliaには改善が加えられより良い言語になっていくと思いますが、現時点でもパッケージが作られ、ユーザは増えつつあるようです。 さらに、Juliaの安定版v1.0を作るべく、Julia Computingは$600,000の資金を獲得しています。
懇親会では以前からブログ等で知っていた@dhichikaさん、@sinhrksさん、@berobero11さんたちと初めてお会いできて楽しい懇親会になりました。
主催してくださった@gepuroさん、缶コレさん、それに会場を提供してくださったリクルート社さん、本当にありがとうございました。
Joneswareの世界
Julia Advent Calendar 2日目の記事です。
JoneswareというのはJulia界の重要人物のひとりであるdcjonesさんの作ったパッケージをさしています。 実は私のJulia Summer of Codeプロジェクトのメンターになってくれた方で、今年のJuliaConでお会いしたこともあります。
彼の書くプログラムは非常に洗練されていて、その設計やスタイルは非常に学ぶところが多いです。 その上、ハイパフォーマンスなコードばかりで、Juliaで技術計算を始める方は是非参考にしてみると良いと思います。
グラフィックスなど
Gadfly.jl
最初は皆さんご存知のGadfly.jlです。 これはggplot2のような"Grammar of Graphics"のアイディアに則ったプロットのためのパッケージです。 具体例はマニュアルを見ていただけると分かるのですが、簡潔なAPIで目的のプロットを行うことができます。
plot(x=rand(10), y=rand(10), Geom.point, Geom.line)
Compose.jl
Compose.jlはベクター画像を作るためのパッケージです。 Gadfly.jlはこのCompose.jlの上に作られています。 Compose.jlそれ自体でかなり有用なパッケージですので、ここでもご紹介しましょう。 Jupyter NotebookとIJulia.jlを使うと、実際に以下のプロットを試すことができます。
Compose.jlでは、図形を組み合わせて、ベクター画像を作ります。
たとえば、polygon([(1, 1), (0, 1), (1/2, 0)]
は3つの頂点 (1, 1), (0, 1), (1/2, 0) を持つ多角形を作ります。
そして、context()
でその図形が置かれる座標系を指定でき、compose()
で座標系と図形を組み合わせます:
context(x0, y0, width, height)
のように座標系を指定できるため、以下のようにcontext(0, 1/2, 1/2, 1/2)
とするとy軸方向に1/2ずらした、幅と高さが1/2の座標系となります:
複数の図形を組み合わせるのも簡単で、compose
の呼び出しをネストするだけです:
再帰呼び出しと組み合わせて、シェルピンスキーの三角形のようなフラクタル図形も描けます:
fill
などで色などの属性も指定できます:
このように複雑な図形を単純な要素の組み合わせで表現できるため、ベクター画像で何かを可視化したい時はこのパッケージを使うと良いでしょう。 Jupyter NotebookではレンダリングのためSVGのバックエンドを使っていますが、Cairo.jlなどを通してPNGなどの画像ファイルを出力することもできます。 また、このパッケージに触発された、3D版のパッケージ Compose3D.jlもあります。
その他
他にもLocal regression (loess) というノンパラメトリックな回帰のパッケージLoess.jlや、YAMLのパーサYAML.jl、C言語のようなswitch文を可能にするSwitch.jlも彼のパッケージです。
バイオ関係
Jonesさんはバイオインフォマティクスを研究している博士課程の学生(のはず)なので、バイオ関係のパッケージもいくつも作っています。 ここではそれらのパッケージの紹介をしていきます。
Bio.jl
バイオインフォマティクスのライブラリ、Bio.jlも私にとっては重要です。Jonesさんはこのパッケージの非常に重要な部分を作っています。 このパッケージの紹介はまた今度書きましょう。
JuliaConでのトークは、YouTubeで公開されています。
IntervalTrees.jl
IntervalTrees.jlは区間木の実装です。バイオインフォマティクスでは、ゲノムや染色体のある区間を扱いたいことがよくあります。 例えば、ヒトの遺伝子とエキソン領域のアノテーション情報をもとに、見つかった変異がどの遺伝子の何番目のエキソン領域にあるかを知りたい場合などです。
オーバーラップしている区間を取り出すにはintersect
を使います:
julia> using IntervalTrees julia> im = IntervalMap{Int,ASCIIString}() IntervalTrees.IntervalBTree{Int64,IntervalTrees.IntervalValue{Int64,ASCIIString},64} julia> im[(1, 100)] = "foo" "foo" julia> im[(50, 100)] = "bar" "bar" julia> im[(101, 100)] = "baz" "baz" julia> collect(intersect(im, (10, 20))) 1-element Array{Any,1}: IntervalTrees.IntervalValue{Int64,ASCIIString} (1,100) => foo julia> collect(intersect(im, (10, 80))) 2-element Array{Any,1}: IntervalTrees.IntervalValue{Int64,ASCIIString} (1,100) => foo IntervalTrees.IntervalValue{Int64,ASCIIString} (50,100) => bar
このパッケージはBio.jlでもゲノムのアノテーション情報などを保持するのに使われています。
BufferedStreams.jl
BufferedStreams.jlはIOのバッファリングをより積極的に行い、IOを高速化します。 次に述べるLibz.jlやRagelで使われています。
Libz.jl
Libz.jlはzlibへのバインディングですが、速度に非常に重点を置いています。 インターフェースも良い感じで、例えば圧縮されたテキストファイルを一行ずつ扱うには以下のようにできます:
using Libz let filepath = shift!(ARGS) open(filepath) do s s′ = ZlibInflateInputStream(s) for line in eachline(s′) @show line end end end
Ragel
Ragelは正規言語からパーサを生成するコンパイラです。 と言っても、別に正規文法しか使えないわけではなく、有限オートマトンの状態遷移の間にホスト言語での操作を挟めるため、わりと色々パースできると思います。
このコンパイラ自体はAdrian D. Thurston氏のプロダクトなのですが、そのJulia版サポートを書いたのがJonesさんなわけです。彼はJuliaをRagelに対応させるためにgoto文までJuliaに実装しています。 今のところ、このレポジトリからJulia対応版を使うことができますが、次のRagelのリリースではJuliaが正式にサポートされる予定です。
例えば、FASTA形式のファイルをパースする場合は、以下のように記述することができます:
action count_line { state.linenum += 1 } action mark { Ragel.@anchor! } action identifier { Ragel.@copy_from_anchor!(output.name) } action description { Ragel.@copy_from_anchor!(output.metadata.description) } action letters { Ragel.@append_from_anchor!(input.seqbuf) } newline = '\r'? '\n' >count_line; hspace = [ \t\v]; whitespace = space | newline; identifier = (any - space)+ >mark %identifier; description = ((any - hspace) [^\r\n]*) >mark %description; letters = (any - space - '>')+ >mark %letters; sequence = whitespace* letters? (whitespace+ letters)*; fasta_entry = '>' identifier (hspace+ description)? newline sequence whitespace*; main := whitespace* (fasta_entry %finish_match)**;
Ragelを使えば、pure Juliaで高速なパーサを生成することができるため、様々なフォーマットのファイルを扱うバイオインフォマティクスでは大変意義のあるツールです。 正式版が出たら、また解説を書くかもしれません。
Julia v0.4 新機能&変更点まとめ
待ちに待ったJuliaの新バージョンv0.4がリリース直前です!
現在はRC1RC2が利用可能ですので、新しいもの好きの方はダウンロードして試すことができます。
Releases · JuliaLang/julia · GitHub
v0.4はかなり長い間開発されてきましたので、新機能が盛りだくさんになっています。 すべての変更点を紹介することはできませんので、完全なリストは本家のリリースノートを参照してください。 注目度の高いものから順に見て行きましょう。
ドキュメント機能とパッケージのコンパイル機能追加!
パッケージを作る人にもユーザにも嬉しい機能追加がこの2つです。 ドキュメント機能は今まで標準ライブラリにしかなかったドキュメントが、パッケージ作成者や他のユーザも追加できるようになりました。 また、パッケージのコンパイル機能では、サードパーティーのパッケージが初回の実行時に自動的にコンパイルされるようになり、パッケージの読み込み時間がかなり短縮されるようになりました。
ドキュメント機能
ドキュメント機能では、ドキュメントをメソッドや型の定義の部分に書くことができます。
例えば、以下のようにPoint
型と、distance
というユークリッド距離を求める関数を書いたとしましょう。
""" Point type. This is a type to represent a point in 3D Cartesian coordinate system. Fields: * `x`: coordinate of X axis. * `y`: coordinate of Y axis. * `z`: coordinate of Z axis. """ type Point x::Float64 y::Float64 z::Float64 end "Enclidean distance from the origin." function distance(p::Point) sqrt(p.x^2 + p.y^2 + p.z^2) end
ドキュメントはMarkdown形式で記述でき、型やメソッドの直前に文字列リテラルとして記述します。
こうしたドキュメントは、JuliaのREPLからもヘルプ機能(?
コマンド)で参照できます。
パッケージのコンパイル機能
パッケージのコンパイル機能では、パッケージの作者がモジュールがコンパイル可能であることを明示的に示すことによって、初回の利用時に自動的にコンパイルされます。 また、パッケージに変更があった場合には、それを検出して次の利用時に自動的にコンパイルしますので、古いコードが利用されるなどの心配はありません。
パッケージの作者は、__precompile__()
関数をモジュール定義の直前に呼び出すだけでこの新機能に対応できます。
ただし、互換性の問題からVERSION >= v"0.4.0-dev+6521" && __precompile__()
と書くことが推奨されています。
私は、バージョン番号を覚えるのが面倒なので、isdefined(Base, :__precompile__) && __precompile__()
と書いています。
実例はこちらsrc/DocOpt.jlを参照してください。
実際に試してみると、DocOpt.jlのような小さなライブラリでも、読み込み時間が半分以下に短縮されています。
~/v/julia (master|…) $ julia3 -e 'tic(); using DocOpt; toc()' elapsed time: 1.093200985 seconds ~/v/julia (master|…) $ julia4 -e 'tic(); using DocOpt; toc()' elapsed time: 0.430147529 seconds
- Module initialization and precompilation: http://julia.readthedocs.org/en/release-0.4/manual/modules/#module-initialization-and-precompilation
言語機能の強化
v0.4には言語機能の強化がいくつか入りましたので、いくつか重要なものをかいつまんで紹介します。
関数呼び出し構文のオーバーロード
call
というメソッドを定義することで、関数呼び出しの構文をオーバーロードすることができます。
これは、x(...)
という構文がcall(x, ...)
というように変換されますので、対応するcall
メソッドを定義するだけで利用できます。
この機能は特にコンストラクタを定義する際に便利で、例えば配列の生成がVector(Int, 10)
ではなくVector{Int}(10)
のように呼べるようになりました。
これは、call{T}(::Type{Vector{T}}, m::Integer) = ...
というメソッドが定義されているためです。
関数の生成機能 (@generated
)
少しわかりにくい機能なのですが、極めて強力な新機能のひとつに生成関数 (generated function) というものがあります。訳語は適当です。 これは、メタプログラミングのひとつで、引数の型の組み合わせから式を生成するというものです。 以下の様な対応表を考えてみると、少し分かりやすいかもしれません。
機能 | ドメイン → コドメイン |
---|---|
関数 | 値 → 値 |
マクロ | 式 → 式 |
生成関数 | 型 → 式 |
生成関数の式への展開は初回の呼び出し時のみ起こり、それ以降は普通の関数のように使えます。
より詳しい仕組みはJuliaCon 2015のJake Bolewski氏のトークを参照してください。
Jake Bolewski: Staged programming in Julia - YouTube
- Generated functions: http://julia.readthedocs.org/en/release-0.4/manual/metaprogramming/#generated-functions
コンストラクタとしてのconvert
メソッド
コンストラクタの呼び出しが、convert
へとフォールバックするようになりました。
ですので、以下の例を見ると分かるのですが、ある型にconvert
を定義することでコンストラクタのように振る舞うことができます。
convert
の方がコンストラクタより多機能ですので、引数がひとつであり、かつある値の型変換という意味を伴う場合にはconvert
を定義するほうが良いかもしれません。
type Foo x Foo(x::Int) = new(x) end Base.convert(::Type{Foo}, x::Integer) = Foo(convert(Int, x)) @show Foo(1) # ok both on v0.3 and v0.4 @show Foo(0x01) # ok on v0.4, but not on v0.3
- Constructors, Call, and Conversion: http://julia.readthedocs.org/en/release-0.4/manual/constructors/#constructors-call-and-conversion
性能・機能向上など
標準ライブラリや処理系の最適化も進められています。 いくつか重要そうなものを挙げておきましょう。
新しい世代別GCの導入
一新されたGCにより、一回のGCの時間が短縮され遅延が緩和されるようになりました。
SubArray
SubArray
の設計が見直され、様々な操作がより効率的になりました。
これは、先ほど紹介した新機能の生成関数によるところでもあります。
より正確なコード行情報
今までかなりいい加減だったスタックトレースなどのコード行の情報がより正確になりました。 デバッグやパフォーマンスチューニングなどで非常に重要な情報ですのでありがたいです。
その他新機能
言語機能には直接関与しませんが、様々な新機能がv0.4で導入されました。 Juliaをより多目的に使えるようになる機能です。
Nullable{T}
型
値が存在しないことを示す型Nullable{T}
が導入されました。
これはHaskellのMaybe
などに対応するもので、計算の失敗を示す返り値として用いられる事が多いです。
例えば、v0.4で導入されたtryparse
は文字列をうまくパースで来たときには中身のあるNullable
を、失敗した時には空のNullable
を返します。
julia> tryparse(Int, "123") Nullable(123) julia> tryparse(Int, "notint") Nullable{Int64}()
日付や時刻の型
DateTime
やDate
など、日付や時刻を表す型が追加され、様々な演算が定義されています。
例えば、現在時刻はnow
関数で取得できます。
julia> now() 2015-09-13T13:41:42 julia> typeof(now()) DateTime
明示的な関数のインライン化
関数のインライン化は以前からありましたが、新しく導入された@inline
マクロを使うことでコンパイラに関数のインライン化を強制できます。
くり返し呼ばれる関数では@inline
を使うことでパフォーマンスが向上することがあります。
使い方は簡単で、関数定義の直前に@inline
とつけるだけです。
@inline function getindex(x::T, i::Integer) ... end
その他変更
構文や名前の変更で重要なものをまとめておきます。
辞書の構文
v0.3では["one" => 1, "two" => 2]
のように辞書を書いていたのですが、この構文が非推奨になりました。
v0.4ではDict("one" => 1, "two" => 2)
のようにコンストラクタ呼び出しを使います。
Union
、Tuple
の構文
v0.3ではUnion(T1, T2)
、Tuple(T1, T2)
のように書いていたのですが、一貫性のためそれぞれUnion{T1, T2}
、Tuple{T1, T2}
という構文に変更されました。
型に関しては一貫して{ }
を使うようです。
型名の変更
Uint
やUint32
などの名前がそれぞれUInt
, UInt32
という名前に変更になりました。
前の名称を使っても特に警告はありませんが、将来的にはすべて後者に置き換えられると思いますので、UInt
やUInt32
などと書きましょう。
また、String
がAbstractString
に変更されました。
int
やfloat
などの関数が非推奨
v0.3では、文字列や型の変換にint
やfloat
関数を使っていたのですが、これが非推奨となりました。
文字列をパースしてInt
型に変換する際にはparse(Int, str)
を、別の数値型からInt
にする際はInt(x)
を利用してください。
Juliaのシンボルとは?
先ほど何となくstackoverflowのJuliaに関する投稿を見ていたら、なるほど確かに最初は分からないかもしれないなと思う疑問と、それに対する分かりやすいKarpinski氏の回答があったので紹介しようと思います。
シンボル(Symbol
)とは何か
Juliaを使っていると、シンボルというよく分からない型に出会うことがあります。
PythonやRを使っている人にとっては、初めて目にするものかもしれません。
Rubyを使っている人は名前は知っていると思いますが、Juliaではまた違った使い方がされます。
例えば、REPLで:foo
と打って、型を確認するとそいつが出現します。
julia> :foo :foo julia> typeof(:foo) Symbol
この Symbol
とは一体何であって、なんのために使うのでしょうか?
ひとことで言えば、『シンボルはメタプログラミングにおいて変数を表すのに使われる (a symbol is used to represent a variable in metaprogramming)』ものです。
これだけでは分からないので、少し例を出しつつ説明していきましょう。
Juliaは、偉大なプログラミング言語でアイデアの源泉であるLispから多くのものを受け継ぎました。
それは、プログラム自体をプログラムで操作する能力です。
JuliaはプログラムのコードをJuliaのデータ構造として持つことができ、コードを書き換えることができます。
:(...)
もしくはquote ... end
のようにコードをラップ(quote: クォート)することで、そのコード自体を表現するデータ構造を作り出すことができます。
コード x + 1
をクォートすると :(x + 1)
になるといった具合です。
このデータ構造は Expr
と命名されています。
julia> :(x + 1) :(x + 1) julia> typeof(:(x + 1)) Expr
このデータ構造を詳しく見てみると、以下の様な構造を指定ます。
julia> xdump(:(x + 1)) Expr head: Symbol call args: Array(Any,(3,)) 1: Symbol + 2: Symbol x 3: Int64 1 typ: Any::DataType <: Any
ここに、3つのシンボル(Symbol
)が出てきていることに気がつくと思います。
call
については、Juliaが付加したものですが、+
とx
は両方とも元々のコード x + 1
に含まれていたシンボルです。
Expr: - `call` (Symbol) - `+` (Symbol) - `x` (Symbol) - `1` (Int64)
個々のシンボルは、以下のように取り出すこともできます。
julia> ex = :(x + 1) :(x + 1) julia> ex.args[1] :+ julia> ex.args[2] :x julia> ex.args[3] 1
+
や x
といったシンボルは、元々のコード x + 1
の中で変数として使われていたものです (+
のような演算子もJuliaでは他の変数と変わりありません)。
すなわち、シンボルはJuliaのコードの変数を表すのに使われるデータということになります。
そう考えると、Juliaのコードは数値 (42
) や文字列 ("foo"
) などのリテラルやコメントを除くとほとんどシンボルだということがわかると思います。
const
や=
でさえも、Julia内部ではシンボルとして扱われています。
julia> xdump(:(const W = WORD_SIZE)) Expr head: Symbol const args: Array(Any,(1,)) 1: Expr head: Symbol = args: Array(Any,(2,)) 1: Symbol W 2: Symbol WORD_SIZE typ: Any::DataType <: Any typ: Any::DataType <: Any
シンボルをどう使うか
Juliaのシンボルは二通りの使われ方をします。ひとつはコードの中での効率的な文字列としてですが、より重要なのがメタプログラミングでの利用です。
先ほど述べたように、Juliaのコードは Expr
というデータ構造で表現できるため、Juliaからこのデータ構造を操作することでプログラムを自由に作ることができます。
さらに、これも先ほど見たようにコードの多くはシンボルで構成されるため、シンボルをうまく操ってプログラムを生成する必要があるわけです。
Rubyなどでもシンボルはあるようなのですが、Rubyでは主に String interning としてシンボルを使っているようです。
Expr
とシンボルを使ってコードを生成してみましょう。
先ほどみた x + 1
というコードを生成してみます。
julia> Expr(:call, :+, :x, 1) :(x + 1)
Expr
を使ってコードができることが分かりました。
コードは実行することができるはずです。これには eval
関数を使います。
julia> eval(Expr(:call, :+, :x, 1)) ERROR: UndefVarError: x not defined
変数 x
が無いというエラーになりました。
では x
の値を定義して、もう一度やってみましょう。
julia> x = 10 10 julia> eval(Expr(:call, :+, :x, 1)) 11
今度は問題なく実行できました。
もっとも、 Expr
を使ってコードを生成するより :(...)
や quote ... end
を使ったほうが楽なので、通常はこちらを使います。
しかし、こういうものを使う場合でも、自分がシンボルを含んだ式を作っているということをはっきりと意識することが重要だと思います。
macro
によるマクロの定義と @<macro>
によるマクロ呼出しで、生成したコードをその場に埋め込むことができます。
julia> macro x_plus_one() :(x + 1) end julia> @x_plus_one 11
マクロはREPLで使うこともありますが、実際のコードでは以下のように関数定義の中で呼び出すことが多いでしょう。
julia> function plus_one(x) @x_plus_one end plus_one (generic function with 1 method) julia> plus_one(3) 4
@x_plus_one
マクロは、 x + 1
というコードを生成しますから、以下のように関数の引数を y
にしてしまうと違った動作をします。
julia> function silly_plus_one(y) @x_plus_one end silly_plus_one (generic function with 1 method) julia> silly_plus_one(3) 11
このとき、silly_plus_one(y)
関数は以下の定義と同じ意味ですから、引数は無視されて外側の変数 x
を拾ってしまうことになります。
:(x + 1)
という Expr
が、 :x
というシンボル名を使っていることを強く意識することが重要です。
function silly_plus_one(y) x + 1 end
もうちょっとシンボルを使った簡単なメタプログラミングの実例を見てみましょう。
次のコードは、AからZまでのアルファベットのASCIIコードを保持する定数 ord_A
, ord_B
, ..., ord_Z
を生成するプログラムです。
julia> for char in 'A':'Z' @eval const $(symbol(string("ord_", char))) = $(Int(char)) end julia> ord_A 65 julia> ord_G 71
AからZまですべての定数を手で書いたら大変ですので、メタプログラミングの技法を使うわけです。
ここでは、定数名 ord_<X>
をまず文字列として作り、そこからシンボルを symbol
関数で生成しています (この部分は symbol("ord_", char)
でも問題ありませんが、説明のため一度文字列として変数を生成してからシンボルに変換しています)。
julia> string("ord_", 'A') "ord_A" julia> symbol(string("ord_", 'A')) :ord_A
$(...)
はスプライシング(splicing)や挿入(interpolation)などと言って、コードを評価してその部分に差し込むことができる仕組みです。
例えば、最初の char = 'A'
の段階では、
@eval const $(symbol(string("ord_", 'A'))) = $(Int('A'))
すなわち、
@eval const $(:ord_A) = $(65)
となって、これが quote ... end
と eval
の機能を合わせた @eval
マクロにより
const ord_A = 65
と変換されて、通常の定数の宣言のように振る舞います。 このように、シンボルを使ってコードの変数を生成することで、Julia自身でJuliaのプログラムを自由に生成することができます。
Juliaのメタプログラミングを理解するためにはシンボルをちゃんと理解することが欠かせません。 メタプログラミングをしなくても十分Juliaは強力な言語ですが、これらを使いこなすことでさらに冗長なコードを排除したりパフォーマンスを上げることも可能です。 メタプログラミングについては、Juliaのマニュアル Metaprogramming に詳細な説明がありますので、このあたりに興味のある方は是非確認してみてください。