Ruby
IronRuby

IronRubyのコードを読んでみよう

More than 1 year has passed since last update.

IronRubyはMicrosoftの開発した.NetFramework上で動くRubyの処理系ですが、

ちょっと触ってコードを読んで遊んでいたのでそのまとめです。

現在でも下のURLでインストーラーをダウンロードしてインストールすると、普通に動く。

http://ironruby.net/

.NetFramework4.0で追加された、動的言語ランタイム(DLR)を基盤として、.NetFrameworkを使った動的言語作成の実験結果として2007年に披露された。

現在はGIthub上にコードが公開されていて、IronPythonと合わせて公開されていて、pull requestも受け付けられている。

https://github.com/IronLanguages

という状態ですが、実際に触って、それから中のプログラムも読んで見たのでその感想。


使ってみた実感

まず、以外に動く。

とりあえず普通にCRubyの基本的なコードを転がしてみる。

1 + 2 # => 3

[1,2,3].map { |i| i * 2 } # => [2,4,6]

動いている。

.Net環境下なので普段C#等で使っている、.Netのライブラリが普通に呼べる

require 'mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'

require 'System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
System::Console.WriteLine("Hello World!") # => 「Hello,World!」の文字が表示される

GUI部品も普通に呼べるので、GUIアプリケーションが作れる。

require 'System.Windows.Forms, Version=2.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089'

System::Windows::Forms::MessageBox.Show("Hello,World!") # => GUIのダイアログが表示される。

ここら辺の資料は、以下参照

参照リンク:

http://www.atmarkit.co.jp/fdotnet/special/ironruby/ironruby_02.html

Rubyのメタプログラミング的な機能も動く

# define_methodの定義実験

class Hoge
define_method :mage do
10
end
end
Hoge.new.mage # => 10が返る

# sendメソッドの実行実験

1.send(:to_s) # => "1" が返る

# Object#extend の実験

a = 1.0
a.extend(Module.new do
def fuga
2
end
end)
a.fuga # => 2 が返ってくる

# method_missingの定義実験

def method_missing(name, *_)
print "throw method_missing : #{name}"
end
hogehoge() # => 「throw method_missing : hogehoge」が印字される

.Netの標準ライブラリにもObject#extendが適応できたりします。

モンキーパッチングも同様に動く

require 'System.Windows.Forms, Version=2.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089'

System::Windows::Forms::MessageBox.extend(Module.new do
def foo
self.Show("Foo")
end
end)
System::Windows::Forms::MessageBox.foo # => Foo と表示されたダイアログが表示される

refinementは動かないので、だいたいRuby2.0程度の実装状態

Module#prependやぼっち演算子(&.)なんかも動かない。


細かい非互換の所


define_methodがクラス宣言の中でないと動かない

なんのクラス宣言も無い所でいきなりdefine_method使うと何故か落ちる

define_method :foo do

"Hello"
end
# 以下の様にエラーが印字される
# (ir):1 undefined method `define_method` for main:Object (NoMethodError)


「何でも」Object#extend出来る

現在のRubyは、nil,true ,false,Fixnumの値は、Object#extendは出来ないけど、IronRubyでは可能。

module FixnumExtend

def hoge(*_)
2
end
end
a = 1
a.extend(FixnumExtend) # => hogeメソッドが拡張されてしまう
a.methods # => hogeがリストに存在する

# Trueクラスも同じ
true.extend(Module.new do def foo; :fool end; end)
true.foo

こういう感じで、基本的に、互換性は高いが、初心者が普通に書いている感じでは踏まないコードでは、挙動の異なるところは結構見つかる。

実装を考えればある意味こちらの方が合理的なように出来ている。


コードを読んでみよう

GIthubからコードをcloneすると、普通にダウンロードできる。

https://github.com/IronLanguages/ironruby

VisualStudioを用意すればビルド可能(試行錯誤必須)

これの動かしながらコードを解読下が、今回は.Net Frameworkとどう協調しているかをテーマに読み込んでいく。


DLRの基本

動的言語ランタイムが作成したコードは

動的言語ランタイム(DLR)は、構文解析したコードを一旦、中間構文木(AST)に変換する

※ 中間構文木はDLRのライブラリにあるNodeクラスを継承して作る

あとは構文木に対する動作をActionクラスを囲って作って紐つける

これで構文と挙動が一致するので、あとはDLRが"よしな"に実行してくれる。

DLRの上にどう工夫をしてRubyのオブジェクトの"柔らかさ"を実装したのか

共通言語ランタイムとどう相互運用性を取ったのか


IronRuby

Rubyは一度定義したクラスに、後出しでメソッドやプロパティを追加、削除、再定義可能で、型に対してルーズ。

静的型付き言語はその逆。

でも、.Netのライブラリをそのまま読み込んで改変可能。

これを実現するために、IronRuby内部でRubyClassというクラスを緩衝材として設けている。

public sealed partial class RubyClass : RubyModule, IDuplicable {

https://github.com/IronLanguages/ironruby/blob/master/Src/Ruby/Builtins/RubyClass.cs#L47

これがRubyのClassクラスの実装、通常のRubyの実装と同じでModuleクラスの派生クラスでModuleクラスの中で、Rubyっぽいオブジェクトの扱いの定義はおおよそされている。

クラスの名前

private string _name;

https://github.com/IronLanguages/ironruby/blob/master/Src/Ruby/Builtins/RubyModule.cs#L142

superクラス

 private readonly RubyClass _superClass;

https://github.com/IronLanguages/ironruby/blob/master/Src/Ruby/Builtins/RubyClass.cs#L52


メソッドに関して

中の実装は、変数として_methodsというのを保持していて、ここでメソッドへの参照一覧を管理していた。

private Dictionary<string, RubyMemberInfo> _methods;

https://github.com/IronLanguages/ironruby/blob/master/Src/Ruby/Builtins/RubyModule.cs#L274

RubyMemberInfoが、様々な方法(C#で実装されたもの、rubyで定義されたもの)で定義されているメソッドのラッパーになっている。

プロパティも同じように_propertiesという変数で一括管理されている。

クラスをロードしたら、そのクラスのメソッドがどれか一つでも呼ばれたタイミングで、中のメソッドを、一括で_methodsに取り込んでる。

実際の実装はGetClrMethodsメソッドの中で行われている。

private static IEnumerable<MethodInfo/*!*/>/*!*/ GetClrMethods(Type/*!*/ type, BindingFlags bindingFlags, bool inherited, string/*!*/ name) {

return _clrMethodsByName.GetMembers(type, name, inherited).WithBindingFlags(bindingFlags);
}

https://github.com/IronLanguages/ironruby/blob/master/Src/Ruby/Builtins/RubyClass.cs#L1079

GetMembersメソッドを使って、Reflectionでメソッドへの参照を手に入れています。

Reflection経由で呼び出すために発生する速度低下は

属しているメソッドが一つでも呼ばれたらそのタイミングですべてのメソッドをまとめてキャッシュして、軽減していますね。

まとめて取得してキャッシュしておくのは速度のバランスとしてはそんなものだと思います。

結論:リフレクションを使えば動的言語と静的言語の橋渡しは可能。

その時のパフォーマンスチューニングは色々知恵を凝らしておいた方がいい。


オチ

現在のIronRubyの現状は検索してみるとこういうのが出てきます。

IronRuby はご臨終でしょうか ? - Qiita

実は最後にコミットが入ったのは2012年。そのあと去年IronPythonと隔離分離された状態です。

そういう感じなので、Ruby2.1以降の機能は追加されていない。

動く実装ではあるが現在の最新のRubyの仕様に追随できているわけではない。

動的言語として簡潔で素直なIronPythonの方がむしろこのプロジェクトの中心。

クラスの柔らかさなど変態要素の多いRubyは教科書通りにいかないところを拡張しながら作成されていてDLRのパワーを素直に使っていない。

JRubyとは違って何故か受け入れられなかったと言っていいと思います

(JRubyが生きていることの方がむしろ謎)

別に今からIronRuby自体に投資する必要はない。

動的言語を他の実装系に移植するときのサンプルと思って読んでおくと、すり合わせのために頑張ったところ、やパフォーマンスを見て設計変更したところのノウハウは学ぶところが多い。

特にDLRを使わないで、わざわざ内部で拡張している部分は、Rubyのクラスに関してだった。

Rubyのアイデンティティはクラスの構造にあると感じた。

※ 今日のまとめ

まぁ得るものは多かったけど、JRuby先に読んだら良かったのではともちょっと思ったりしました(⌒-⌒; )