2014-09-16
【Ruby】Rubyで配列言語(Array Language)を実現する
[ 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
posted with amazlet at 14.09.16
Paolo Perrotta
アスキー・メディアワークス
売り上げランキング: 114,408
アスキー・メディアワークス
売り上げランキング: 114,408