2014-09-16

【Ruby】Rubyで配列言語(Array Language)を実現する

Categories: Ruby MATLAB
sin-array.png

[ PR ]


いきなりですが、配列言語 (Array Language)というのを知っていますか?

MATLABがその代表例で、MATLABの無料クローンである Scilabで実行してみた結果が以下です。

-->sqrt(2)
 ans = 1.4142136 

-->sqrt([1,2,3,4,5])
 ans = 1.  1.4142136  1.7320508  2.  2.236068  

凄くないですか?

一つの値を引数としても、配列を引数としても普通に計算出来ています。

これをRubyで実現させてみようと思います。

基礎

いきなりコードを書くと困惑してしまうので、単純な例を挙げます。

class Shape
  def initialize(x,y)
    @x = x
    @y = y
  end

  def to_s
    "Shape(#{@x},#{@y})"
  end
end

puts "\n=== [1] Single Shape ==="
shape = Shape.new(7,7)
p shape

puts "\n=== [2] Multiple Shapes ==="
points = [[1,1],[1,2],[3,4]]
shapes = points.map do |p|
  Shape.new(p[0],p[1])
end
p points
p shapes

puts "\n=== [3] Calculate Single Value ==="
num = 3
p num
p Math.sqrt(num)

puts "\n=== [4] Calculate Multiple Values ==="
nums = [1,2,3,4,5]
p nums
result = nums.map do |i|
  Math.sqrt(i)
end
p result

単なる値の時は普通に計算し、配列のときはmapメソッドで回して(Iterate)計算します。

実行結果は以下の通りです。

=== [1] Single Shape ===
#<Shape:0x007fd38901de98 @x=7, @y=7>

=== [2] Multiple Shapes ===
[[1, 1], [1, 2], [3, 4]]
[#<Shape:0x007fd38901dc18 @x=1, @y=1>, #<Shape:0x007fd38901dbf0 @x=1, @y=2>, #<Shape:0x007fd38901dbc8 @x=3, @y=4>]

=== [3] Calculate Single Value ===
3
1.7320508075688772

=== [4] Calculate Multiple Values ===
[1, 2, 3, 4, 5]
[1.0, 1.4142135623730951, 1.7320508075688772, 2.0, 2.23606797749979]

実装

では、実際に実装してみたいと思います。

基本方針は以下の通りです。

  • 直接引数を渡さず、Argクラスでラップ(Wrap)して渡す
  • クラスの初期化は、Clazz.newを使わず、Clazz::instanceを使う
  • コンストラクタ(initialize)は普通に書く
  • instanceクラスメソッドで、Iterator::instanceを呼び、そちらに初期化作業を委譲(Delegate)する(例:Iterator::instance(Shape,args)
  • メソッド・関数から値を返すときは、生データを返さずに、Argクラスでラップして返す。そうしないとメソッドのチェーン(連鎖)ができない。 (←これが最も重要)

では実装をします。

UPDATE: Arg.initialize(*args)の引数を、可変引数から一つの固定引数(*args => args)に変更しました。それに伴ってコードと出力結果が変わっています。

class Arg
    def initialize(args)
        if args.is_a? Array
            @args = args
        else
            @args = [args]
        end
    end

    def get()
        @args
    end
end

class Iterator
    def self.instance(clazz,args)
        if args.is_a? Arg
            result = args.get.map do |arg|
                clazz.new(arg)
            end

            Arg.new(result)
        else
            raise "Args should be instance of Arg"
        end
    end
end

class Shape
    def self.instance(args)
        Iterator::instance(Shape,args)
    end

    def initialize(args)
        @x = args[0]
        @y = args[1]
    end

    def to_s
        "Shape(#{@x},#{@y})"
    end
end

def sqrt(arg)
    result = arg.get.map do |i|
        Math.sqrt(i)
    end

    Arg.new(result)
end

puts "\n=== [1] Single Shape ==="
shape = Shape::instance(Arg.new([7,7]))
p shape

puts "\n=== [2] Multiple Shapes ==="
args = Arg.new([[1,1],[1,2],[3,4]])
shapes = Shape::instance(args)
p args
p shapes

puts "\n=== [3] Calculate Single Value ==="
num = Arg.new(3)
p num
p sqrt(num)

puts "\n=== [4] Calculate Multiple Values ==="
nums = Arg.new([1,2,3,4,5])
p nums
p sqrt(nums)

このコードの実行部分は、Scilabとそっくりだと思いませんか?

特に、

sqrt(Arg.new(3))
sqrt(Arg.new(1,2,3,4,5))

と書けるのが素晴らしいです。

ちなみに、実行結果は以下の通りです。


=== [1] Single Shape === #<Arg:0x007f8131202c90 @args=[#<Shape:0x007f8131202d58 @x=1, @y=1>, #<Shape:0x007f8131202cb8 @x=1, @y=1>]> === [2] Multiple Shapes === #<Arg:0x007f8131202858 @args=[[1, 1], [1, 2], [3, 4]]> #<Arg:0x007f8131202768 @args=[#<Shape:0x007f8131202808 @x=1, @y=1>, #<Shape:0x007f81312027b8 @x=1, @y=2>, #<Shape:0x007f8131202790 @x=3, @y=4>]> === [3] Calculate Single Value === #<Arg:0x007f8131201f98 @args=[3]> #<Arg:0x007f8131201e30 @args=[1.7320508075688772]> === [4] Calculate Multiple Values === #<Arg:0x007f8131201c78 @args=[1, 2, 3, 4, 5]> #<Arg:0x007f8131201a70 @args=[1.0, 1.4142135623730951, 1.7320508075688772, 2.0, 2.23606797749979]>

値をラップしてあるので、少し見づらいですが、きちんと結果が返っていることがわかります。

生データを表示したいときは、Arg.getメソッドを使うといいです。

ちなみに、Scalaなどの関数型言語によくある、Maybeモナドっぽい実装にしました。そうすることで、中身を隠ぺいすることができます。

まとめ

MATLAB風の配列言語をRubyで実装する方法を紹介しました。

配列言語について詳しく知りたい場合は、http://www.scilab.org/ をダウンロードして遊んでみてください。

メタプログラミングRuby
Paolo Perrotta
アスキー・メディアワークス
売り上げランキング: 114,408

コメントはTwitterアカウントにお願いします。

RECENT POSTS


[ PR ]

.