プログラミング言語 Julia 探訪

きっかけ

Julia をさわることにした。初見解説記事という謎ジャンルです。

Julia に興味を持ったのは、このへんのいい話を読んだのがきっかけ。プログラミング言語にはもっと物語があるといいと思う。

Why I’m Betting on Julia

なぜ僕らはJuliaを作ったか - 丸井綜研

科学技術計算に強みのある汎用言語で、スピードと楽に書ける感じを重視してるって感じですかね。競合するのは R, MATLAB, Python あたりか。若い言語で確かにいいとこ取りっぽさがある。同図像性を持つのが特徴的らしい *1

インストール

Julia Downloads

Windows へのインストール

バイナリが配布されていますがインストーラではなく自己解凍ファイルみたいです。パス通しましょう。

OS X へのインストール

公式サイトからバイナリを落とす。dmg をクリックして起動。/Applications フォルダに Julia-x.x.x.app を叩き込む。パスの通ってる場所から以下の場所にシンボリックリンクを貼って適当に解決 *2

$ ln -s /Applications/Julia-0.2.0.app/Contents/Resources/julia/bin/julia julia
$ julia --version
julia version 0.2.0

Contents

言語としての側面に触れていきます。科学技術計算やマクロみたいな便利情報はまったく出てきません。

  1. 数値にさわる
  2. 多重ディスパッチ
  3. Julia の言語デザイン

1 はドキュメントの内容そのまんま。2 は REPL をいじりながら考えたこと。3 は偉い人の意見を読んだ話。

資料

The Julia Manual — Julia Language 0.2.0 documentation

印刷用 : julia.pdf

Julia 本Vol.1 の公開 - yomichi's blog

日本語で読める文献。実践的な内容でかなりおすすめ。

起動

さあはじめよう。コマンドで

$ julia

とだけ入力すると REPL (対話的実行環境) が立ち上がる。まずここで遊ぶ。

Juliaのかわいい画像

カラフルでかわいいですね!

ちなみにファイルを実行する方法は拡張子 jl で保存して、

$ julia hello.jl

と実行する。スクリプト言語らしい簡単さですね。

1. 数値にさわる

Julia は数値型を一通り提供している。8 bit ~ 128 bit までの intfloat をサポートしている。

リテラルの書き方によって自動的に型が決まる。

julia> 1    # => 整数リテラル
julia> 1.0  # => 浮動小数点リテラル

int, float リテラルのデフォルトの大きさは Julia がインストールされているシステムに依存している。

 # 32ビットシステム:
julia> typeof(1)
Int32

# 64ビットシステム:
julia> typeof(1)
Int64

WORD_SIZE に整数のデフォルトの大きさが格納されている。また REPL では型シグネチャを入力しても整数の大きさを確認することができる。

# 32ビットシステム:
julia> Int
Int32
julia> Uint
Uint32


# 64ビットシステム:
julia> Int
Int64
julia> Uint
Uint64

1.1 型の最大値, 最小値

Julia では typemax, typemin によってその型の最大値と最小値を取得できる。

julia> typemax(Int)
9223372036854775807

julia> typemin(Int)
-9223372036854775808

julia> typemax(Uint) # unsigned int
0xffffffffffffffff

julia> typemin(Uint)
0x0000000000000000

これはちょっと面白いというか、他言語では見たことがある気がしない組み込みの関数だ。

Julia はわりとかゆいところまで手の届く言語のようだ。

  • typemax(Bool) を試してみよう

1.2 桁あふれ

Julia は桁あふれについてもわかりやすい仕様を定めている。整数型は モジュラ演算 の形式で扱われている。要するに桁あふれすると時計が一周回ったみたいに値が変わるのだ。

julia> x = typemax(Int64)
9223372036854775807

julia> x + 1
-9223372036854775808

julia> x + 1 == typemin(Int64)
true
  • typemin(Int) - 1 を試してみよう

その他にも、Julia のドキュメントは機械イプシロンや計算丸め、多倍長計算や無限大についていくつか興味深い文献を引きながら紹介している。これは計算機の基本的な話だが、 Julia はこれらについて言語仕様とともによく文書化しており、便利な関数や特殊変数も用意している。つまり Julia は計算機の数値型やその精度について勉強するのに非常によい教材なのだ。この点については気が向いたらまとめたい。

1.3 ブール型、文字型

真偽値をとるブール Bool と文字型を表す Char も内部的に数値で表現されている。だが、これらは数値型とは別のもののようだ。例えば if の条件文で数値を使うことはできない。

julia> true == 1
true

julia> if true
         println("True.")
       end
True.

julia> if 1
       end
ERROR: type: non-boolean (Int64) used in boolean context

Char は 32 bit の Unicode (デフォルトはUTF-8) で表現されている。これは例えば C 言語の文字型 char (8 bit) とは全く異なる。

使う分には日本語も問題なく使えるくらいの認識でいいのではないだろうか (ぐんにゃり)。

2. 多重ディスパッチ - なぜ 'a' + true == 'b' なのか

プリミティブな型には飽きてきたので、Julia に特有の話を見ていこう。

さて、Julia ではこんな計算ができてしまう。

julia> typeof(true)
Bool

julia> true + true          # むむむ、数値に表現が変わった
2

julia> false - true
-1

julia> typeof(true + true)  # Int64 に型変換されておる
Int64

julia> 'a' + true           # !?
'b'

true が 1, false が 0 という仕様は他の言語 (Python) でもよくあることなのでまあいいだろう。しかし、最後の計算が暗黙の型変換っぽくて一見やばい感じがする。'a' + true == 'b' がエラーにならないのは正直かなり理解に苦しむ。やはり動的型付け言語、型付けはなあなあなのだろうか ...

そう思ったそこのあなた! 安心して欲しい (?) これは暗黙の型変換などではなく、Julia の主要機能の一つ、多重ディスパッチ (multiple dispatch) によって実現されているメソッドに過ぎないのだ。

以降では、'a' + true が実際には何をしているのかということに焦点を当てて話を進める。

2.1 多重ディスパッチとはなにか

多重ディスパッチを知る前に、まず関数とメソッドの違いを理解する必要がある。Julia において関数とメソッドは区別される。他言語の関数を多少知っているという前提で話す。

関数

関数の文法は普通だ。

function f(x, y)
  x + y
end

 # あるいは
f(x, y) = x + y

後者は数学関数っぽい見た目で書ける良さがあるが、副作用も持てるただのプログラム関数である。

注意すべきは、ここでは型について何も書かれていないということだ。Julia は動的型検査を行う言語であるので、引数に型を指定する必要がない。

そしてJulia にとって、+ のような一見算術演算子に見えるものも、関数オブジェクトに過ぎない

julia> 1 + 2
3

julia> +(1, 2)  # 関数として呼び出せる
3

julia> f = +    # f の定義は上の REPL のやつが残ってるとエラー
+ (generic function with 92 methods)

julia> f(1, 2)
3

Julia における関数とはこのようなものである。型が指定されていない オブジェクトを取り、何かをする。型の違いを気にしないジェネリックな引数を取るのが関数である

では Julia のメソッドとは何か。語弊を恐れずにいえば、それは型の違いを気にするものである。

メソッド

ジェネリックな関数は一見便利だが、問題がある。それは最初に見た通りだ。

julia> 'a' + true  # 復習:(+) は関数
'b'

この型の変換規則は一見謎である。もし処理系が暗黙的に変換しているのなら、プログラマはそれを把握するのが難しいと感じるだろう。

型変換の問題はいたるところに現れる。例えば算術演算を考えて欲しい。Float と Int の足し算をするときはどういう計算になるのか? *3 Float32 と Float64 の足し算は? 足し算だけでない、数値型なら四則演算や指数関数やその他数学関数はいくらでもあり、それらすべてで問題が現れる。

メソッドの多重ディスパッチはこうした問題を現実的に解決するためにある。

メソッドは型を指定することによって複数宣言できる。

julia> f(x::Int64) = -x
f (generic function with 1 methods)

julia> f(x::Float64) = 2x
f (generic function with 2 methods)

julia> f(1)     # Int64
-1

julia> f(1.0)   # Float64
2.0

julia> f(0x1)   # 定義されていない型のメソッドはエラー
ERROR: no method f(Uint8,)

Julia は、引数の型とその数によって、これらの複数定義されたメソッドのうち、どれを使えばよいか自動的に判別する。これこそが 多重ディスパッチ である。

多重ディスパッチの利点は次のようなことが挙げられるだろう。

  1. メソッドの引数の型を明示できる
  2. 型変換が明示的になる
  3. メソッドの定義がトップレベルに来るのできわめて直感的。レシーバが必要なくなり、オブジェクトを副次的な存在と見なすことができる

静的型言語に慣れ親しんだ人に言わせれば「返り値の型がないじゃんダメじゃん」という声が聞こえてきそうである。

しかし、動的型検査を行う言語としてはこれは相当に洗練されたパッチシステムだと思う (苦しい)。

話を本筋に戻すと

我々がずっと考えてきたのは 'a' + true がどういう意味を持つのか、ということである。関数やメソッドの説明もそのための準備であった。

ここでのメソッドと多重ディスパッチについての議論から、'a' + true+ とは、たんに +(x::Char, y::Bool) で定義されているメソッドに過ぎないと結論できる。このことはプログラマに少なからぬ安心感を与える。Julia のソースコードに型変換の規則が記されていると保証されており、処理系だの暗黙の型変換だのといった難解な世界に立ち入る必要がないからだ。

では +(x::Char, y::Bool) の実際の定義はどこにあるのか?

2.2 多重ディスパッチの見つけ方、あるいは Julia の便利関数

methods を使って + がどんな型に対して定義されているか見ることができる。

julia> help(methods)    # methods に限らず、関数の簡単な使い方はhelp で見ることができる
Base.methods(f)

   Show all methods of "f" with their argument types.

julia> # methods には関数 f を渡したらいいことをあなたは理解したはずだ

julia> methods(+)
+(x::Bool) at bool.jl:35
+(x::Bool,y::Bool) at bool.jl:38
+(x::Union(Array{Bool,N},SubArray{Bool,N,A<:Array{T,N},I<:(Union(Range1{Int64},Range{Int64},Int64)...,)}),y::Union(Array{Bool,N},SubArray{Bool,N,A<:Array{T,N},I<:(Union(Range1{Int64},Range{Int64},Int64)...,)})) at array.jl:982
...
(引数の型ごとに定義された型シグネチャ。92 種類の (+) メソッドの長い出力)
▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂うわあああああああ
...

目をしばしばさせながら +(x::Char, y::Bool) という型シグネチャを探してみるも、なんと存在しなかった *4。いやいやいやいや、存在しなかったら 'a' + true みたいなわけの分からない計算ができるはずがないんだ。

幸いにも、メソッドの存在を示す method_exists が使える。

julia> help(method_exists)
Base.method_exists(f, tuple) -> Bool

   Determine whether the given generic function has a method matching
   the given tuple of argument types.

   **Example**: "method_exists(length, (Array,)) = true"

julia> method_exists(+, (Char, Bool))
true

そして簡易 help には書かれていないものの、methods には第 2 引数に型のタプルを渡すことができる。これで 2 つの型の間に定義されている + メソッドの場所を特定できる。

julia> methods(+, (Char, Bool))
1-element Array{Any,1}:
 (x::Char,y::Integer) at char.jl:30

この出力は CharBool の間の足し算 + がどこに定義されているかを示している。なぜ同じようなことを違う言い回しでくどくど説明するのかというと、それが重要なことだからである。methods という関数はとても便利であり、本質的であり、Julia を理解するのにとても役立つように思える。ここはいくら強調してもし過ぎることはない。helpmethods を使い倒しましょう! *5

2.3 Julia の標準ライブラリを読む

さて、+(x::Char,y::Integer) は、 char.jl における 30 行目に定義されている、ということをここまでに確認した。char.jl ってどこのだよ!と思われるかもしれない。それは Julia が使う標準ライブラリbase 下にある。

baseソースコードは Julia をインストールしたディレクトリを探せばある。ディレクトリの微妙に深いところにあるので、見つけるのが面倒な場合は GitHub に飛ぼう。

julia/base at master · JuliaLang/julia

julia/base/char.jl at master · JuliaLang/julia

+ の定義は次のようになっている。

+(x::Char   , y::Integer) = char(int(x)+int(y))

xy を明示的にキャストしているのが分かる。もうほとんどお分かりだと思うが、擬似的に処理の内容を書き下してみよう。

'a' + true
=> char(int('a') + int(true)) 
=> char(97 + 1)
=> char(98)
=> 'b'

これは「Julia における型変換に魔術的なところは無く、完全に明示的に行われる *6 」という言葉の通りである。Julia は動的型検査を行う言語である (にもかかわらず|がゆえに) 、きわめて奇麗に型変換の問題を扱っていることが確認できた。

... いやいやいや、騙されないぞ。なんでこれが Char, Bool 間の + の定義になっているのか? 右側は Integer だろう。型が違うじゃないか。

適当に推測すると、BoolInteger のサブ型だからマッチするのだろう。

julia> Bool <: Integer
true

3. Julia の言語デザイン

Stefan Karpinski という Julia のコア開発者が多重ディスパッチの哲学について語っている。ここではそれをなぞる形で紹介する。

セッションの跡地: Julia: The Design Impact of Multiple Dispatch - Strange Loop *7

こちらに ipynb の内容:The Design Impact of Multiple Dispatch

多重ディスパッチとは何であったか。

  • 動的 - 実行時に型検査を行う
  • 多重 - すべて引数に基づき、レシーバーに頼らない

多重ディスパッチは関数 *8 の適用を尊重する考え方だ。

a.f(b, c)   <== こうじゃなくて
f(a, b, c)  <== こうする

3.1 多重ディスパッチはオブジェクト指向である

多くのオブジェクト指向言語は多重ディスパッチのまねごとをしているといえる。

オブジェクト指向は、

  • 簡単に、自然な方法で複雑な多態的ふるまいを表現している
  • それは煎じ詰めれば、ディスパッチとデータの派生型を作ることに他ならない

しかし、それらの言語たちは次の重要な点を逃しているのだ。

  • コードをデータとして扱うことができない。そのせいで eval などというものを持っているものもある。

3.2 式の問題

Mads Torgersen の論文 The Expression Problem Revisited を引いているがこの長さの英文になるとなに言ってるかよく分からなくなってくる頭いたい。

To which degree can your application be structured in such a way that both the data model and the set of virtual operations over it can be extended without the need to modify existing code, without the need for code repetition and without runtime type errors.

データモデル = 型、virtual operations = 関数、と考えると、片方を重視すると片方の構造化が難しくなるよーということだろうか (?)

  1. 新しい型 を既存の演算 (関数) に適用すること -> オブジェクト指向では簡単だが関数型では難しい
  2. 新しい演算 を既存の型に適用すること -> 関数型では簡単だがオブジェクト指向では難しい

そしてこれらの二律背反的な問題の一つの処方箋として、多重ディスパッチが考案されたと言いたいのだろう。

3.3 Julia による解決

ここでおもむろに次のような型について考えてみる。

immutable Interval{T<:Real} <: Number
  lo::T
  hi::T
end

(a::Real)..(b::Real) = Interval(a,b)

Base.show(io::IO, iv::Interval) = print(io, "($(iv.lo))..($(iv.hi))")

見慣れないキーワードが出てきたので 調べてみましょう 軽く説明しておくと、immutable は変更不能なフィールドを持った合成型を定義している。

Tジェネリックな型パラメータで、Real の派生型という制限が加えられている。Interval 自体は Number の派生型である。

.. というのが Interval に定義されているメソッドだ。

julia> 1..2
(1)..(2)

julia> (1//2)..(2//3)   # Rational 型. Rational <: Real <: Number
(1//2)..(2//3)

これはうまくいっているように見える。だが、異なる派生型を渡すとうまく動かない。

julia> (0.5)..1
ERROR: no method Interval{T<:Real}(Float64,Int64)
 in .. at none:1

Interval というのは何か数直線上の範囲的なものを表現していると思われる。左端と右端は同じ数値型でなくてよいと普通考えるだろう。ゆえに Interval へ与えられる Number の派生型が異なるだけではじいてしまうのは好ましくない。しかし、Number の派生型の種類は少なくないので、それらすべての組み合わせについて多重ディスパッチを書くのは苦痛である。

ではどうするか。Julia のプロモーションという機構がそれを解決する。

3.4 プロモーション

promote を用いて、異なる型を持つ値を、より大きな共通の型に変換することができる。より大きな共通の型とは、スーパータイプや抽象型のことではない。どちらかの型が取りうる値ををすべて表現できるほうの型、という意味である。例えば、Int64 の値は Float64 によってすべて表現できるので、 promote することができる。

julia> promote(0.5, 1)  # 1 :: Int64 が 1.0 :: Float64 になる
(0.5, 1.0)              # :: (Float64, Float64)

プロモーションを用いて、すべての派生型の組み合わせに適用できる Interval が書ける。

julia> Interval(lo::Real, hi::Real) = Interval(promote(lo,hi)...)
Interval{T<:Real} (constructor with 2 methods)

julia> (0.5)..1
(0.5)..(1.0)

たったこれだけだ。

コンストラクタさえ書けてしまえば、あとはやりたい演算を思いのままに作れる。

julia> a::Interval + b::Interval = (a.lo + b.lo)..(a.hi + b.hi)
+ (generic function with 93 methods)

julia> a::Interval - b::Interval = (a.lo - b.hi)..(a.hi - b.lo)
- (generic function with 107 methods)

julia> (2..3) + (-1..1)
(1)..(4)

julia> (2..3) - (1..2)
(0)..(2)

3.5 プロモーションの力

さらに考えよう。Interval と数値型との算術演算はどうしよう?

julia> 1 + (2..3)   # これを動くようにしてみたい
                    # デザインとしては
                    # ((2+1)..(3+1)) => (3)..(4)

promote を使ってどうにか解決できないだろうか?

しかし残念ながら、Interval のようなユーザー定義の型では、プロモーションはうまく働かない。

julia> promote(1, 0.5..1)
ERROR: no promotion exists for Int64 and Interval{Float64}
 in promote_result at promotion.jl:145
 in promote_type at promotion.jl:110
 in promote at promotion.jl:123

実は、数値型同士の promote がうまくいっていたのは、Julia の側でプロモーション規則を定めていたからである。

ではどうするのか。promote_rule によって Real => Interval にするプロモーション規則を自分で定めればよい (Number でなく Real にする理由は分かるだろうか?)。

import Base.promote_rule

promote_rule{A<:Real,B<:Real}(::Type{Interval{A}}, ::Type{B}) = Interval{promote_type(A,B)}
promote_rule{A<:Real,B<:Real}(::Type{Interval{A}}, ::Type{Interval{B}}) =
    Interval{promote_type(A,B)}

ここで promote_type とは名前の通りプロモーションの型版である。つまり 2 つの型を受け取ってより大きな共通の型を決めるものである。

プロモーションの規則を決めただけでは終わらない。Julia はまだ Interval について共通の型の見つけ方を知っただけなので、これを変換する規則を書いてやる必要がある。それは convert メソッドをディスパッチすることによって行う *9

import Base.convert

convert{T<:Real}(::Type{Interval{T}}, x::Real) = (x = convert(T,x); x..x)
convert{T<:Real}(::Type{Interval{T}}, iv::Interval) =
    convert(T,iv.lo)..convert(T,iv.hi)

これにて IntervalReal の間のすべての型変換の組み合わせが書けてしまった。スゴイ!

julia> (1..2) + 2
(3)..(4)

julia> (1..2) + 1.5
(2.5)..(3.5)

julia> big(2)^100 + ((1//3)..(2//3))
(3802951800684688204490109616129//3)..(3802951800684688204490109616130//3)

Julia スゴイ!

... じゃなくて。実際のところ convertpromote_rule は何をやっているのか。

それを知らないとプロモーションを理解しているとは言えない。

3.6 コンバージョンとプロモーションの詳細

最後の計算の詳細を追ってみる。

julia> big(2)^100 + ((1//3)..(2//3))
(3802951800684688204490109616129//3)..(3802951800684688204490109616130//3)

まず計算結果の型を見てみよう。

julia> typeof(ans)      # REPL の最後の結果は ans で参照できる
Interval{Rational{BigInt}} (constructor with 1 method)

見た目通りである。日本語に直せば「多倍長整数で表される有理数型の区間」となるだろう (分かりづらい)。

これが結果の型で、どうしてこれが導かれるのかが問題。

@which マクロは、関数が適用されている式をそのまま取り、それがどこのメソッドを呼び出しているか教えてくれる。とても便利である。

julia> @which big(2)^100 + ((1//3)..(2//3))
+(x::Number,y::Number) at promotion.jl:148

呼び出されたのが +(x::Number,y::Number) at promotion.jl:148 というのがミソである。

では、+ の左右両側の型を見てみよう。

julia> typeof(big(2)^100)
BigInt (constructor with 7 methods)

julia> typeof((1//3)..(2//3))
Interval{Rational{Int64}} (constructor with 1 method)

ところで、 BigIntInterval の間には + を適用することができない。

なぜならそんなメソッドは定義していないからだ。

じゃあどうして計算できるのか?

それは、関数 + には、メソッドを探すのに失敗したとき呼び出される fallback メソッドというものが存在するからだ。at promotion.jl:148 で呼び出された + とはこの fallback メソッドのことだったのだ。

base/promotion.jl の該当行を見てみよう。+ は次のように定義されているはずである。

+(x::Number, y::Number) = +(promote(x,y)...)

promote メソッドがコールされている。2 引数に対する promote は次のように定義されている。

promote{T,S}(x::T, y::S) =
    (convert(promote_type(T,S),x), convert(promote_type(T,S),y))

もしかしたら型パラメータと変数の内容を忘れかけているかもしれない。

このケースでの promote の値を露骨に書き下すと次のようになる。

promote{T,S}(x::T, y::S) =
  (convert(promote_type(BigInt, Interval{Rational{Int64}}, big(2)^100 :: BigInt)),
   convert(promote_type(BigInt, Interval{Rational{Int64}}, ((1//3)..(2//3)) :: Rational{Int64})))

この promote_type の返り値が promote_rule によって決まるのだが、この処理は少し複雑なのでここでは触れない。

重要なのは、promote_rule を見れば、プロモーションによる型の変換規則はすべて分かるということである。

# たらい回し的に呼び出される promote_type の図

# A = Rational{Int64}, B = BigInt
promote_rule{A<:Real,B<:Real}(::Type{Interval{A}}, ::Type{B}) = 
    Interval{promote_type(A,B)}

# Int64 = A, BigInt = B
promote_rule{A<:Integer,B<:Integer}(::Type{Rational{A}}, ::Type{B}) =
    Rational{promote_type(A,B)}

# Int64 より BigInt が大きい
promote_rule{T<:Integer}(::Type{BigInt}, ::Type{T}) = BigInt

promote_type の結果を promote_rule に沿って手を使って置き換えてみよう。Interval{Rational{BigInt}} になっただろうか?

julia> promote_type(BigInt, Interval{Rational{Int64}})
Interval{Rational{BigInt}} (constructor with 1 method)

promote の処理をまた露骨に書き下す。

promote{T,S}(x::T, y::S) =
  (convert(Interval{Rational{BigInt}}, big(2)^100 :: BigInt)),
   convert(Interval{Rational{BigInt}}, ((1//3)..(2//3)) :: Rational{Int64}))) 

あとは convert を行うだけでよいことが分かる。 big(2)^100 :: BigIntInterval{Rational{BigInt}}convert するメソッドは自明ではないだろう。となればディスパッチを書けばいいだけである。そしてこのディスパッチはセクション 3.5 ですでに書いたものである。

長い追跡だったが、これにて promote の型変換は解決した。2 つの値は Interval{Rational{BigInt}} という共通の型に変換できることが分かった。

3.7 まとめ・Julia の言語デザインとは

式の問題

まずセクション 3.2 における式の問題の結論はどうなるのだろうか。

  1. 新しい型 を既存の演算 (関数) に適用すること -> オブジェクト指向では簡単だが関数型では難しい
  2. 新しい演算 を既存の型に適用すること -> 関数型では簡単だがオブジェクト指向では難しい

元のノートは多くを語らない。セッションを見ることができればよいのだが。

ここからは関数型もオブジェクト指向もあまり詳しくない私の愚見を述べる *10

Julia は多重ディスパッチによって、オブジェクト指向であるにも関わらず、レシーバを意識せずに関数を書けるようになっている。

これは既存のオブジェクト指向にはあまりない強みであろう。Julia ではメソッドが第一義的な存在であるとすら言えるのだ。メソッドはオープンであり、誰でもディスパッチできるのでやりたいことに集中することができる。

では、 Julia にあって関数型言語におそらくない強みとは何か。それはプロモーションである。プロモーション規則によって、新しく定義したオブジェクト=型をどんどんメソッドに取り込むことができるのだ。擬似的に関数型アプローチを取っているものの、Julia のオブジェクトとメソッドは非常に親和性が高い。

Julia のデザイン・インパク

原文では Julia のデザイン・インパクトについて少し触れている。それは、

  • 関数をよりプロトコルらしく書ける
  • 演算の意味 (meaning) を考えるのが難しくなり、それがどういうものなのか説明しづらくなる

説明が難しいとはどういうことか。例えば search の使い方を見てみる

julia> help(search)
Base.search(string, chars[, start])

   Search for the first occurance of the given characters within the
   given string. The second argument may be a single character, a
   vector or a set of characters, a string, or a regular expression
   (though regular expressions are only allowed on contiguous strings,
   such as ASCII or UTF-8 strings). The third argument optionally
   specifies a starting index. The return value is a range of indexes
   where the matching sequence is found, such that "s[search(s,x)] ==
   x":

   *search(string, "substring")* = *start:end* such that
   "string[start:end] == "substring"", or *0:-1* if unmatched.

   *search(string, 'c')*         = *index* such that "string[index]
   == 'c'", or *0* if unmatched.

第 1 引数は探す対象文字列だが、第 2 引数はジェネリックな関数であるために単一の文字から文字のベクトルまたは集合、あるいは文字列、あるいは正規表現まで受け取ることができる。これは強力といえば強力だが、一目で使い方を理解できないので難しいとも言えるだろう。

おわりに

冒頭にもリンク貼りましたが第 3 セクションは翻訳ではありません。けっこう飛ばしているし、個人的に難しいと思ったところは無駄に行間を埋めまくっています。議論の本筋とは関係ない便利情報とかは抜かしています。確認してみると面白いですよ!

The Design Impact of Multiple Dispatch

感想

えいやっ、と勢いだけでがつがつ書いてみた。しかし 1. を書き始めた時はドキュメントの上澄みをさらっとなぞるだけのつもりが、どんどん脇道に逸れてしまった ...

我流の説明を試みたのは 2. の多重ディスパッチだけで、興味のあるところを一夜漬け深さ優先で掘り進んだ感じだが、プロモーションにもたどり着けなかったしなんかなんというか外した説明をしてしまった気がする。うーん。

それにしても、軽く触ってみるつもりが思いのほか最高の言語っぽいぞ Julia 。お世辞じゃなく。

*1:詳しくないが要するにメタプログラミングしやすいということではないだろうか

*2:適当じゃない解決法を知らない

*3:これは言語によって異なる。暗黙の型変換を行う言語もあれば (弱い型付けという) 、そんなことはできない、といってつっぱねる言語もある (強い型付けという)

*4:記事を書きながら調べてるので割と焦った

*5:実際には、@which マクロのような便利なマクロがあるようです. 私もまだ詳細は知りません

*6:all conversion in Julia is non-magical and completely explicit. link

*7:ところで InfoQ のページはこんなこと言ってるけど全然公開されてないじゃないですかやだー "This is a restricted presentation that can only be viewed by Strange Loop 2013 attendees! The public release of this presentation will be in 3 months."

*8:Julia ではメソッドだが便宜上混同したくなる

*9:promote_rule と convert は REPL 上で定義したらうまくいかなかった。仕方ないのでファイルにまとめて実行した

*10:へりくだっているがマジでしょうもない意見だ