IronRubyはMicrosoftの開発した.NetFramework上で動くRubyの処理系ですが、
ちょっと触ってコードを読んで遊んでいたのでそのまとめです。
現在でも下のURLでインストーラーをダウンロードしてインストールすると、普通に動く。
.NetFramework4.0で追加された、動的言語ランタイム(DLR)を基盤として、.NetFrameworkを使った動的言語作成の実験結果として2007年に披露された。
現在はGIthub上にコードが公開されていて、IronPythonと合わせて公開されていて、pull requestも受け付けられている。
という状態ですが、実際に触って、それから中のプログラムも読んで見たのでその感想。
使ってみた実感
まず、以外に動く。
とりあえず普通に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すると、普通にダウンロードできる。
VisualStudioを用意すればビルド可能(試行錯誤必須)
これの動かしながらコードを解読下が、今回は.Net Frameworkとどう協調しているかをテーマに読み込んでいく。
DLRの基本
動的言語ランタイムが作成したコードは
動的言語ランタイム(DLR)は、構文解析したコードを一旦、中間構文木(AST)に変換する
※ 中間構文木はDLRのライブラリにあるNodeクラスを継承して作る
あとは構文木に対する動作をActionクラスを囲って作って紐つける
これで構文と挙動が一致するので、あとはDLRが"よしな"に実行してくれる。
DLRの上にどう工夫をしてRubyのオブジェクトの"柔らかさ"を実装したのか
共通言語ランタイムとどう相互運用性を取ったのか
IronRuby
Rubyは一度定義したクラスに、後出しでメソッドやプロパティを追加、削除、再定義可能で、型に対してルーズ。
静的型付き言語はその逆。
でも、.Netのライブラリをそのまま読み込んで改変可能。
これを実現するために、IronRuby内部でRubyClassというクラスを緩衝材として設けている。
public sealed partial class RubyClass : RubyModule, IDuplicable {
これがRubyのClassクラスの実装、通常のRubyの実装と同じでModuleクラスの派生クラスでModuleクラスの中で、Rubyっぽいオブジェクトの扱いの定義はおおよそされている。
クラスの名前
private string _name;
superクラス
private readonly RubyClass _superClass;
メソッドに関して
中の実装は、変数として_methods
というのを保持していて、ここでメソッドへの参照一覧を管理していた。
private Dictionary<string, RubyMemberInfo> _methods;
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);
}
GetMembersメソッドを使って、Reflectionでメソッドへの参照を手に入れています。
Reflection経由で呼び出すために発生する速度低下は
属しているメソッドが一つでも呼ばれたらそのタイミングですべてのメソッドをまとめてキャッシュして、軽減していますね。
まとめて取得してキャッシュしておくのは速度のバランスとしてはそんなものだと思います。
結論:リフレクションを使えば動的言語と静的言語の橋渡しは可能。
その時のパフォーマンスチューニングは色々知恵を凝らしておいた方がいい。
オチ
現在のIronRubyの現状は検索してみるとこういうのが出てきます。
実は最後にコミットが入ったのは2012年。そのあと去年IronPythonと隔離分離された状態です。
そういう感じなので、Ruby2.1以降の機能は追加されていない。
動く実装ではあるが現在の最新のRubyの仕様に追随できているわけではない。
動的言語として簡潔で素直なIronPythonの方がむしろこのプロジェクトの中心。
クラスの柔らかさなど変態要素の多いRubyは教科書通りにいかないところを拡張しながら作成されていてDLRのパワーを素直に使っていない。
JRubyとは違って何故か受け入れられなかったと言っていいと思います
(JRubyが生きていることの方がむしろ謎)
別に今からIronRuby自体に投資する必要はない。
動的言語を他の実装系に移植するときのサンプルと思って読んでおくと、すり合わせのために頑張ったところ、やパフォーマンスを見て設計変更したところのノウハウは学ぶところが多い。
特にDLRを使わないで、わざわざ内部で拡張している部分は、Rubyのクラスに関してだった。
Rubyのアイデンティティはクラスの構造にあると感じた。
※ 今日のまとめ
まぁ得るものは多かったけど、JRuby先に読んだら良かったのではともちょっと思ったりしました(⌒-⌒; )