第57回勉強会でLTをしてきました

遅刻しましたごめんなさい。(>_<)
そのため、勉強会の最後にトリを務める形でLTをしてきました。
会場のリアクションはいい具合に冷たくて、心地良い死にたさでした。
プログラムを公開して欲しいとのお声を頂きましたので、主要部分をこのエントリで解説し、プロジェクトもダウンロードできる形にしました。


>> CLRH57.zip <<


今回のLTのテーマは、3つの言語の相互運用でした。
特にCLRとDLRの連携が目玉になります。
サンプルとして用意したプロジェクトは、

  • GUIC# で作り
  • 機能を F# で実装し
  • このアプリケーション用のShellを、IronPythonで作る

といった感じです。
C#F#で作られたテキストエディタを、IronPythonで外部からインタラクティブに操作できるようになっています。具体的には、このアプリケーションのインスタンスIronPythonに渡してやり、そのインスタンスを操作します。


操作イメージ図 :




まずC#のコードを載せますが*1、こちらはほとんど何もしていません。
handlerという変数に、F#で実装されたクラスのインスタンスが格納されています。

public partial class Form1 : Form
{
	CoreModule.Handler handler = null;

	public Form1()
	{
		InitializeComponent();
		this.handler = new CoreModule.Handler( this.textBox );
	}

	private void button_Python_Click(object sender, EventArgs e)
	{
		var scriptEditor = new ScriptEditor() { Handler = this.handler };
		scriptEditor.Show();
	}

	private void button_Open_Click(object sender, EventArgs e)
	{
		this.handler.Open();
	}

	private void button_Clear_Click(object sender, EventArgs e)
	{
		this.handler.Clear();
	}
}


次のF#がメインになります。
全機能の実装、およびIronPythonとの連携を担当しているというのに、何もしていないC#のコードよりも短くなっています

[<CompiledName("Handler")>]
type ``F# is beautiful !!``(textBox : TextBox) as self =

    let python = Python.CreateEngine()
    let scope  = python.CreateScope()
    do scope.SetVariable("handler", ObjectHandle(self))

    member self.Write(x)     = if x <> null then textBox.Text <- textBox.Text + string x
    member self.WriteLine(x) = if x <> null then self.Write(string x + "\r\n")
    member self.Clear()      = textBox.Clear()
    member self.Load(path)   = self.Clear() ; File.ReadAllText(path) |> self.Write

    member self.Open() =
        use dialog = new OpenFileDialog()
        if dialog.ShowDialog() = DialogResult.OK then
            self.Load(dialog.FileName)

    member self.EvalPython(expr : string) =
        self.WriteLine <| try python.Execute(expr, scope) with e -> e :> obj

画面(エディタ)に出力するWrite, WriteLineメソッドや、内容をクリアするClearメソッド、ファイルの読み込みを行うOpenメソッドなどが定義されています。これらアプリケーションとしての機能の実装とは別に、IronPythonとの連携も行っています。これが最後のEvalPythonメソッドです。任意の式をPythonエンジンに渡して評価し、その結果を画面に出力するようになっています。


これだとただのPython評価器ですので、IronPythonに全知全能を与えてやらねばなりません。
コンストラクタを見てみますと、以下のようになっています。

do scope.SetVariable("handler", ObjectHandle(self))

これは何をやっているかというと、「今動いているアプリケーションのインスタンス(self)」を"handler"という名前で参照できるように!しています。こうすることによって、IronPythonはアプリケーションを操作する権利を得ます。
ただ単にインスタンスをセットしようとすると、値がディープコピーされてしまいます。そうなると今実際に動作しているアプリケーションとは別のインスタンスを操作することになりますので、画面には何も反映されません。そこで、参照を渡すためにSystem.Runtime.Remoting.ObjectHandleクラスでラップしています。


こうするとIronPythonで好き放題にできます。
例えば、

for i in range(0, 10) :
  handler.Write("F#!")

とやればF#の出力メソッドが呼ばれて、画面に10個出力されますし、

handler.Open()

とすればファイルを選択するダイアログが開きます。



以上です。こんな感じ。
もちろんDLRはこのような使い方しかできないわけではありませんが、発想次第で以上のようなオレオレShellを作ることもできます。自分のシステム/アプリケーションに対話環境を用意したい場合は、1から自作するよりもIronPythonなどを利用したほうが随分と楽です。同様にして、IronRubyやIronSchemeを利用することもできます。

*1:主要部分だけ載せます