2010-05-12[n年前へ]
■オブジェクト指向のRubyに、数式処理ソフトウェアMathematicaの機能を自然な形で取り入れてみよう!?
「Rubyで数独ソルバを書いてみる」でMathematicaで書かれたコードをRubyに翻訳してみました。その作業をしながら考えたことは、「Mathematicaの機能をRubyに自然に取り込むとしたら、一体どういう言語仕様になるのだろうか」ということです。すべてのものが「リスト」であり、基本的に関数型言語であるMathematicaの機能を、多くのものを「オブジェクト」として扱うオブジェクト指向のRubyに(Rubyになじんだやり方で)取り入れるとしたら、一体どんな記述になるのだろう?という疑問です。
RubyとMathematicaを繋いだものとして、Ruby/Mathematicaがあります。Ruby/Mathematicaで「Cos(x)をxで積分した結果」を求めるコードは下のようになります。
math=Mathematica::Mathematica.new.start puts math.eval_foreground('Integrate[Cos[x], x]')このコードは'Sin[x]'を出力しますが、「オブジェクト指向」のRubyに溶け込んでいるとは言えない記述になっています。
そこで、頭の整理がてら、もしもMathematicaのコードをRubyに溶け込ませるとしたらどうなるかを考えながら、「Mathematicaの機能を(.NETベースの)IronRubyに取り込むコード」をつらつら書いてみました。
書いたコード(ライブラリ)は後述することとして、まずは、そのコードを使った例を紹介してみることにします。たとえば、「Cos(x)をxで積分した結果」を求めるなら、こんな感じの記述になります。
p 'x'.Cos.Integrate 'x'これは、'xという文字列オブジェクトにCosメソッドを適用し、さらに"x"で積分するというメッセージを投げる、という具合の記述です。コードをそのまま読んでいくなら、「'x'のコサイン関数をxで積分する」という具合になります。
あるいは、'2 x + 3 == 0'という方程式を、xについて解くなら、
'2 x + 3 == 0'.Solve 'x'という具合の記述になります。何だか、少し「Rubyみたいな」コードに見えてくるのではないでしょうか?
同様に、「(Rubyネイティブの!)配列"[[1,2,3],2,3]"について、それぞれのサイン関数を求め、数値化する(その結果を出力する)コード」なら、こんな具合です。
p ([[1,2,3],2,3]).map.Sin.Nここで、"map"はRubyのメソッドで、"Sin"と"N"はMathematicaの関数です。RubyのメソッドとMathematicaの関数が入り混じっていることがわかります。それらは、頭文字が大文字なのがMathematicaの関数で、頭文字が小文字なのがRubyの関数ということで区別することができます。RubyとMathematicaでは命名規則が異なるので、名前がぶつかることはありません。 ちなみに、
p 3.Times(10).Plus 5というコードを書けば、これは「3に10を乗算した結果に5を足した結果を出力する」ということになります。この"Times"も"Plus"も、あたかもRubyっぽく見えますが、いずれもMathematicaの機能(関数)です。
さて、オブジェクト指向のRubyにMathematicaの機能を自然な形で取り入れるために、試しに書いてみたコードは、下のようになります。
…下のコードを要約すると、メソッド名が見つからない場合に呼ばれるmethod_missingで、「見つからないメソッドがあった場合には、その命令をMathematicaに投げちゃえ」というだけのコードです。つまりは、単に(Ruby上で)未知のメソッドを「右(Ruby)から左(Mathematica)に受け流す」という「ムーディ勝山」方式…ということになります。しかも、オブジェクトの総本山、Objectクラスにその「ムーディ勝山」メソッドをいきなりMix-inしてしまう…という、とんでもないコードです。一発ネタ的コードとして、楽しんで頂けたら幸いです。
# 2010/05/12 jun hirabayashi jun@hirax.net class Mathematica require 'Wolfram.NETLink' include Wolfram::NETLink # ここにMathKernel.exeへのパスを記述する PAR="-linkmode launch -linkname 'C:...\\MathKernel.exe'" def Mathematica.to_m(array) strArray=[] array.each do |item| if item.kind_of? Array strArray<<Mathematica.to_m(item) else if item.kind_of? String strArray<<'"'+item+'"' else strArray<<item end end end '{'+strArray.join(',')+'}' end def Mathematica.to_a str require "JSONParser" jsonParser=JSONParser.new s=str.gsub(/, ([^{]{1})/,',"\1') s=s.gsub(/([^}]{1}),/,'\1",') s.gsub!(/\{([^{]{1})/,'["\1') s.gsub!(/([^}]{1})\}/,'\1"]') jsonParser.parse s end def Mathematica.callback proc { @kernelLink.EvaluateToInputForm 'MVClose[]',0 } end def initialize() ObjectSpace.define_finalizer self,Mathematica.callback @kernelLink=MathLinkFactory.CreateKernelLink PAR @kernelLink.WaitAndDiscardAnswer end def do(q) @kernelLink.EvaluateToInputForm q,0 end end module MathematicaModule def method_missing(name, *args) $mathematica=Mathematica.new unless $mathematica if self.kind_of? Array obj=Mathematica.to_m(self) else obj=self end if name.to_s results=$mathematica.do( [name,'[', ([obj]+args).join(','),']'].join('') ) end return results end def To_a Mathematica.to_a self end end class Object include MathematicaModule end