りんごがでている

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

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

Japan.R 2015でJuliaの布教してきました

Julia Advent Calendar 6日目の記事です。

昨日のJapan.R 2015のLTでJuliaの布教をしてきました。 5分で55枚のスライドをやり切り、時間ピッタリで無事終えました。

だそうです。

当日の資料はSlideShareにあげてあります:

www.slideshare.net

言いたいことは3点で、

  1. Juliaは速い
  2. コミュニティもパッケージも育ってきている
  3. 最近は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)

f:id:bicycle1885:20151201165328p:plain

Compose.jl

Compose.jlベクター画像を作るためのパッケージです。 Gadfly.jlはこのCompose.jlの上に作られています。 Compose.jlそれ自体でかなり有用なパッケージですので、ここでもご紹介しましょう。 Jupyter NotebookIJulia.jlを使うと、実際に以下のプロットを試すことができます。

Compose.jlでは、図形を組み合わせて、ベクター画像を作ります。 たとえば、polygon([(1, 1), (0, 1), (1/2, 0)]は3つの頂点 (1, 1), (0, 1), (1/2, 0) を持つ多角形を作ります。 そして、context()でその図形が置かれる座標系を指定でき、compose()で座標系と図形を組み合わせます: f:id:bicycle1885:20151201171505p:plain

context(x0, y0, width, height)のように座標系を指定できるため、以下のようにcontext(0, 1/2, 1/2, 1/2)とするとy軸方向に1/2ずらした、幅と高さが1/2の座標系となります: f:id:bicycle1885:20151201172045p:plain

複数の図形を組み合わせるのも簡単で、composeの呼び出しをネストするだけです: f:id:bicycle1885:20151201172224p:plain

再帰呼び出しと組み合わせて、シェルピンスキーの三角形のようなフラクタル図形も描けます: f:id:bicycle1885:20151201172341p:plain

fillなどで色などの属性も指定できます: f:id:bicycle1885:20151201172638p:plain

このように複雑な図形を単純な要素の組み合わせで表現できるため、ベクター画像で何かを可視化したい時はこのパッケージを使うと良いでしょう。 Jupyter NotebookではレンダリングのためSVGのバックエンドを使っていますが、Cairo.jlなどを通してPNGなどの画像ファイルを出力することもできます。 また、このパッケージに触発された、3D版のパッケージ Compose3D.jlもあります。

その他

他にもLocal regression (loess) というノンパラメトリックな回帰のパッケージLoess.jlや、YAMLのパーサYAML.jlC言語のようなswitch文を可能にするSwitch.jlも彼のパッケージです。

バイオ関係

Jonesさんはバイオインフォマティクスを研究している博士課程の学生(のはず)なので、バイオ関係のパッケージもいくつも作っています。 ここではそれらのパッケージの紹介をしていきます。

Bio.jl

バイオインフォマティクスのライブラリ、Bio.jlも私にとっては重要です。Jonesさんはこのパッケージの非常に重要な部分を作っています。 このパッケージの紹介はまた今度書きましょう。

JuliaConでのトークは、YouTubeで公開されています。

www.youtube.com

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)**;

fasta.rl

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からもヘルプ機能(?コマンド)で参照できます。

f:id:bicycle1885:20150913124935p:plain

パッケージのコンパイル機能

パッケージのコンパイル機能では、パッケージの作者がモジュールがコンパイル可能であることを明示的に示すことによって、初回の利用時に自動的にコンパイルされます。 また、パッケージに変更があった場合には、それを検出して次の利用時に自動的にコンパイルしますので、古いコードが利用されるなどの心配はありません。

パッケージの作者は、__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

言語機能の強化

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

コンストラクタとしての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

性能・機能向上など

標準ライブラリや処理系の最適化も進められています。 いくつか重要そうなものを挙げておきましょう。

新しい世代別GCの導入

一新されたGCにより、一回のGCの時間が短縮され遅延が緩和されるようになりました。

SubArray

SubArrayの設計が見直され、様々な操作がより効率的になりました。 これは、先ほど紹介した新機能の生成関数によるところでもあります。

より正確なコード行情報

今までかなりいい加減だったスタックトレースなどのコード行の情報がより正確になりました。 デバッグやパフォーマンスチューニングなどで非常に重要な情報ですのでありがたいです。

その他新機能

言語機能には直接関与しませんが、様々な新機能がv0.4で導入されました。 Juliaをより多目的に使えるようになる機能です。

Nullable{T}

値が存在しないことを示す型Nullable{T}が導入されました。 これはHaskellMaybeなどに対応するもので、計算の失敗を示す返り値として用いられる事が多いです。 例えば、v0.4で導入されたtryparseは文字列をうまくパースで来たときには中身のあるNullableを、失敗した時には空のNullableを返します。

julia> tryparse(Int, "123")
Nullable(123)

julia> tryparse(Int, "notint")
Nullable{Int64}()

日付や時刻の型

DateTimeDateなど、日付や時刻を表す型が追加され、様々な演算が定義されています。 例えば、現在時刻は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)のようにコンストラクタ呼び出しを使います。

UnionTupleの構文

v0.3ではUnion(T1, T2)Tuple(T1, T2)のように書いていたのですが、一貫性のためそれぞれUnion{T1, T2}Tuple{T1, T2}という構文に変更されました。 型に関しては一貫して{ }を使うようです。

型名の変更

UintUint32などの名前がそれぞれUInt, UInt32という名前に変更になりました。 前の名称を使っても特に警告はありませんが、将来的にはすべて後者に置き換えられると思いますので、UIntUInt32などと書きましょう。

また、StringAbstractStringに変更されました。

intfloatなどの関数が非推奨

v0.3では、文字列や型の変換にintfloat関数を使っていたのですが、これが非推奨となりました。 文字列をパースしてInt型に変換する際にはparse(Int, str)を、別の数値型からIntにする際はInt(x)を利用してください。

Juliaのシンボルとは?

先ほど何となくstackoverflowのJuliaに関する投稿を見ていたら、なるほど確かに最初は分からないかもしれないなと思う疑問と、それに対する分かりやすいKarpinski氏の回答があったので紹介しようと思います。

stackoverflow.com

シンボル(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 ... endeval の機能を合わせた @eval マクロにより

const ord_A = 65

と変換されて、通常の定数の宣言のように振る舞います。 このように、シンボルを使ってコードの変数を生成することで、Julia自身でJuliaのプログラムを自由に生成することができます。

Juliaのメタプログラミングを理解するためにはシンボルをちゃんと理解することが欠かせません。 メタプログラミングをしなくても十分Juliaは強力な言語ですが、これらを使いこなすことでさらに冗長なコードを排除したりパフォーマンスを上げることも可能です。 メタプログラミングについては、Juliaのマニュアル Metaprogramming に詳細な説明がありますので、このあたりに興味のある方は是非確認してみてください。

株式会社リブセンスのTechNightに参加してきました

リブセンスのエンジニアの方にJuliaの話をしてほしいとの話を受けまして、LTですがJuliaのお話をしてきました。 このようなエンジニア中心の勉強会を毎月行っているようです。

LT大会「TechNight」、7/27(月) 19:30〜開催します。 | LIVESENSE made* f:id:bicycle1885:20150727192213j:plain

せっかくですので、このブログ記事で内容を簡単にまとめておこうと思います。 タイトルは「Juliaは何処から来たのか、Juliaとは何か、Juliaは何処へ行くのか」ということで、Juliaの発端と機能、それに今後どうなっていくのかを簡単にお話しました。

事前にJuliaを知っているかということを尋ねたのですが、20人ほどいるなかで、ひとりだけ去年ちょっと触ってみたという方がいらっしゃいました。 それ以外の方は、Juliaのことを初めて知ったようです。

Juliaは何処から来たのか

Juliaの開発はボストンのMITで始まり、現在でも開発の中心になっています。

f:id:bicycle1885:20150728012347p:plain

Founders

言語を始めにつくった人達は次の4人です。

Jeffはプログラミング言語スペシャリストで、femtolispという非常に小さいSchemeのような言語の実装を作っています。これは、Julia本体のフロントエンド部分にも使われています。 Edelman先生はMITの教授であり、JeffのPh.D.の指導教員でもあるようです。 より詳しい経緯はWIREDの記事にもなっていますので、読んでみてください。

www.wired.com

Repository

開発はすべてGitHubでオープンに行われていて、だれでも参加することができます。

github.com

Why They Created Julia

そもそも何故新しいプログラミング言語が必要なのでしょうか。 その理由は、Juliaの最初のブログ記事で説明されています。

Why We Created Julia

In short, because we are greedy.

一言で言えば、貪欲だからだ。

これは、先ほどのブログの冒頭からの引用です。 どういう意味かは記事を読んでいただけると分かるのですが、まとめると、

  • Rubyのように動的性質を持っていて、
  • Cのように高速で、
  • Lispのようにマクロもあって、
  • Rのように統計が得意で、
  • Perlのように文字列処理もできて、
  • MATLABのように線形代数もできる
  • シンプルでオープンでインタラクティブな言語がほしい

ということになります。そして、それを実現したのがJuliaです。

Juliaとは何か

Juliaの見た目を知るためにクイックソートのコード例を示します。

quicksort(xs::Vector) = quicksort!(copy(xs))

function quicksort!(xs::Vector, lo=1, hi=endof(xs))
    if lo < hi
        p = partition(xs, lo, hi)
        quicksort!(xs, lo, p - 1)
        quicksort!(xs, p + 1, hi)
    end
    return xs
end

function partition(xs, lo, hi)
    pivot = div(lo + hi, 2)
    pvalue = xs[pivot]
    xs[pivot], xs[hi] = xs[hi], xs[pivot]
    j = lo
    @inbounds for i in lo:hi-1
        if xs[i] <= pvalue
            xs[i], xs[j] = xs[j], xs[i]
            j += 1
        end
    end
    xs[j], xs[hi] = xs[hi], xs[j]
    return j
end

多くのスクリプト言語と共通するのは、

  • 簡潔な文法 (MATLAB風)
  • 変数の型宣言が不要
  • 引数の型指定が不要

というところでしょう。 一方、あまり見られない特徴としては、

が挙げられます。 型推論JITコンパイルで、変数の型が決定できるときには非常に高速なコードが生成されます。 マクロはLispに強い影響を受けていて、Cプリプロセッサのような文字列置き換えでなく、式を書き換えることができるものです。 コンパクトなデータ構造というのは、型定義の際にそのデータ構造が占める容量が決定でき、各メンバーへのポインターなど余計なサイズを取らないということです。

Juliaの書きやすさと表現力は、標準ライブラリに現れています。 Juliaの標準ライブラリはほぼすべてJuliaで書かれており、Cのライブラリの呼び出しもJuliaから簡単にできます。 f:id:bicycle1885:20150728015130p:plain

そして、先ほどのクイックソートは、型指定などは無いにも関わらず、とても高速に動きます。 同じように書いたC言語並みの速度です。 さらに、動的にコードを生成しますので、例えば浮動小数点数でなく整数に対しても、コードを変更したりせず高速に動きます。

f:id:bicycle1885:20150728015425p:plain

数値型

元々科学技術計算を目的として作られていますので、数値型が豊富です。 符号付き整数型は8bit, 16bit, 32bit, 64bit, 128bitがそれぞれ用意されており、 Int8からInt128までわかり易い名前がついています。 同様に、符号なしの整数はUint64などと定義されています。 浮動小数点数も16bit, 32bit, 64bitがあり、Float64などと表記されます。 他には複素数有理数、任意精度の数値の型もあらかじめ用意されています。

線形代数

ベクトルや行列の積や線形方程式の解、LU分解やSVDなども標準ライブラリにあり、 実装はOpenBLASやLAPACKなどの高速な実装を呼び出しますので、パフォーマンスも十分です。

多重ディスパッチ

Juliaの根幹をなす重要な機能が多重ディスパッチです。 これは、引数の型の組み合わせにより実行されるメソッドが切り替わるものです。

例えば、標準ライブラリにあるsum関数を考えると、 sum([1,2,3])sum(trues(4))で異なる実装のメソッドが呼び出されます。 [1,2,3]の型はVector{Int}であり、trues(4)の型はBitVectorで、両者で(高速な)和の計算方法は異なります。 そのため、多重ディスパッチを利用して、別々に実装しているわけです。 この時に注目して欲しいのは、引数の型が型推論により決定できる場合は、呼び出しのオーバーヘッドがない点です。 このような特徴から、標準ライブラリでも多重ディスパッチは多用されています。

マクロ

@show@assertなど、デバッグなどに便利はマクロが多数存在しています。 マクロはJuliaの式を実行前に書き換えることができるメタプログラミング機能です。 例えば、@show xとするだけで、変数xの値が、x => 42のように変数名付きで表示されます。 これは、普通の関数には不可能な仕組みです。

コード生成

自分の書いた関数や、ライブラリのコードが実際にどのように実行されるかは、@code_llvm@code_nativeマクロを使えば確認できます。 それぞれ、コンパイルしたLLVM IRとネイティブのコードを表示してくれるため、最適化などの際に非常に重要です。

Juliaは何処へ行くのか

Juliaのバージョンは、以下の様なものがあります。2015年7月末の時点では、v0.3が最新版です。

  • v0.1 ← 未開の地
  • v0.2 ← もはや郷愁の念
  • v0.3 ← いまここ
  • v0.4 ← もうすぐ
  • v0.5 ← 来年・再来年あたり
  • v1.0 ← 未定

v0.4の新機能は、こちらで確認できます。

julia/NEWS.md at master · JuliaLang/julia · GitHub

Ecosystem

現在600以上のパッケージがあり現在も増え続けています。

f:id:bicycle1885:20150728021347p:plain

Julia Package Listing

Conference

先月にはJuliaCon 2015もありました。実ははるばるボストンまで行ってきたのでした。

様々な分野の方々がJuliaを実際に使っていて、とてもよい印象を持っているようです。

JuliaCon 2015: Boston, MA.

強力なスポンサーもついており、ムーア財団やBlack Rock, Intelなどが出資しています。 JuliaConにもGoogle, Facebook, Intelといった会社の方々がたくさん来ていました。

Julia Computing

Juliaの創始者達が、スタートアップを始めたようです。 Juliaのコンサル業などをしており、Julia自体は今後もオープンであり続けるようです。

venturebeat.com

JuliaTokyo

我々はJuliaTokyoというユーザグループを立ち上げ、年に数回勉強会も開いています。 次回はまだ未定ですが、是非都合が合えば参加してみてください。

JuliaTokyo #4 - connpass

質疑

いくつか現地で質疑や雑談で話したことを簡単にまとめておきます。

  • Q1. 去年Juliaを使ったとき(v0.2)、起動がとても遅かったが、いまはどうなのか。
  • A1. 最新リリース版のv0.3では、標準ライブラリにプリコンパイル機能が入ったので、起動時間は大幅に短縮された。さらに次期バージョン(v0.4)では、外部パッケージのプリコンパイル機能が入るので、パッケージの読み込みも高速化する。

  • Q2. Webなどのツールはどうなのか。

  • A2. JuliaWebグループがサーバなどの環境を整備している。Juliaは各種専門グループがいくつかあり、オープンにツールを開発している。

  • Q3. Juliaはどういう使われ方を想定しているのか。

  • A3. Juliaは汎用プログラミング言語になることを目指している。インターフェースからコアの計算までを一つの言語で書けることを目指しつつ、数値計算のみならずPythonのように汎用的に使われるようにしようとしている。また、Juliaを組み込んで使う方法もドキュメントに記載されており、そのように別のツールの内部で使われる方向もあると思う。

  • Q4. 何かJuliaには問題はないのか。

  • A4. パフォーマンス上の落とし穴がいくつかある。基本的には型推論がうまく動かないコードや、オブジェクト生成に関する誤解が原因。マニュアルにPerformance Tipsのセクションがあるので、読むと良い。

  • Q5. JuliaのIDEみたいなのがあった気がするが、どうしたのか。

  • A5. Julia Studioは死んだ。現在はLight Tableとうまく連携するJunoというツールがあるので、これを使うと良さそう。個人的にはvimを使っている。

  • Q6. プレゼンに使っているそのツールはなにか。

  • A6. IPython Notebookというもの。ローカルでJuliaの処理系が動いており、ブラウザからコードを実行し結果を確認できる。最近JupyterというツールとしてPythonに依存しない機能が分離し、JuliaやPythonのみならず、RやRubyなど様々な言語も実行できる。

所感

割りと多くの方がJuliaに興味を持ってくれたようでした。 ウェブの企業だと、プログラミング言語自体の実行性能を気にすることはあまり無いようですが、分析系の人達は今のRなどに不満があって、速くなるのならJuliaを使ってみるのもいいかもしれないと言っていました。 Juliaのマニュアルの日本語化は需要があるようで、完成すれば普及に一役買うかもしれません。

「シリーズ Useful R データ分析プロセス」をいただきました

著者の福島さんより、「データ分析プロセス」(共立出版)の御恵投いただきました。ありがとうございます。 この本はシリーズ Useful Rのうちの一冊のようです。よく見ると私のラボのOBである門田先生もトランスクリプトーム解析の本をこのシリーズで書かれているのですね。

本書の第1章から第5章にかけて、書名の通りデータ分析プロセス全体に関わる事項が網羅されています。 第1章では、データ分析とは何であって実データを扱う上でどのような問題に直面しうるのかを問題提起し、 いくつかの分析フレームワークを紹介しています。

第2章では、R言語を使ってデータフレームの扱いの基本を確認しています。新刊だけあって、伝統的なRの標準パッケージのみならず、比較的最近になってRの世界では一般的になった新しいパッケージの導入もしています。 特にdata.tableとその周辺ツールに関しては、大規模データを扱う際には必須のツールになってきている感じがします。

第3章に入ると、分析前の前処理の話に移り、実データには必ず存在する欠損値と外れ値への対応方法が多くのページを割いて解説されています。 多くの文献を引いて起こりうるパターンと解決方法をコンパクトかつ網羅的に紹介しているので、ここだけでも読む価値があると思います。

続く第4章と第5章が本書のメインになるのでしょう。データから予測モデルを構築する具体的手法と実例を約100ページにわたって解説しています。 ハンズオン・セミナーのように実際にRのセッションへ打ち込みながら結果と説明を読んで、まとまりのある実データでのパターン抽出プロセスと評価方法を学んでいくことができます。 私は特に4.2節の「頻出パターンの抽出」から学ぶところは大きかったです。

全体を通して参考文献も多く引用されているため、この本を足がかりとしてデータ分析の知識を深めていくような読み方もあると思います。 普段やっているデータ分析プロセスを明確にする目的でも、本書を読んでみると良いのではないでしょうか。