インスタンスが属するクラスをあとから変更する操作を Ruby で


Ruby では evil-ruby の #class= が必要になります。また、Ruby には Smalltalk の #become: に相当する機能もないので、Python で使った transmogrify もどき(実装は泥くさいものとなってしまいましたが…^^;)を新たに作り、Smalltalk の #as: っぽい振る舞いとの合わせ技で実現してみました。

require 'rubygems'
require 'evil'

class Object; def as(similar_class); similar_class.new_from(self) end end

class Cartesian
  attr_reader :x, :y

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

  def self.new_from(polar)
    self.new(polar.r * Math.cos(polar.theta), polar.r * Math.sin(polar.theta))
  end
end

class Polar
  attr_reader :r, :theta

  def initialize(r, theta)
    @r = r
    @theta = theta
  end

  def self.new_from(cart)
    self.new(Math.sqrt(cart.x**2 + cart.y**2), Math.atan2(cart.y, cart.x))
  end
end

def transmogrify(a, b)
  a.class = b.class
  a.instance_variables.each{ |v_name|
    a.instance_eval{ remove_instance_variable(v_name) } }
  b.instance_variables.each{ |v_name|
    val = b.instance_eval{ instance_variable_get(v_name) }
    a.instance_eval{ instance_variable_set(v_name, val) }}
  a
end

pos1 = pos2 = Polar.new(Math.sqrt(2), Math::PI/4)
pos1.class   #=> Polar

transmogrify(pos1, pos1.as(Cartesian))
pos1.class   #=> Cartesian
pos1.x       #=> 1.0
pos1.y       #=> 1.0
pos2.class   #=> Cartesian

transmogrify(pos1, pos1.as(Polar))
pos1.class   #=> Polar
pos1.r       #=> 1.4142135623731
pos1.theta / Math::PI   #=> 0.25