Ruby での破壊的メソッドの作り方

自分で作ったクラスに破壊的メソッドを実装するには、インスタンス変数を直接変更するか、それの応用で replaceメソッドを(Stringクラスみたいに)作ってやればいいのだった。

例えば点を表す Pointクラスでは、こんな感じ。o_point_symmetry!メソッドは、点を原点対象の点に移す破壊的メソッド。

#点を表すクラス
class Point
  def initialize(x = 0, y = 0)
    @x = x
    @y = y
  end
  attr_accessor :x, :y

  def replace(pt)
    @x = pt.x
    @y = pt.y
    self
  end
  
  def o_point_symmetry!
    self.replace(Point.new(-@x, -@y))
  end
end

pt = Point.new(50, 100)
pt.o_point_symmetry!
p pt   #=> #<Point:*** @x=-50, @y=-100>

もちろんこれは replaceメソッドを作ってみたかっただけで、以下のようにすれば充分。

#点を表すクラス
class Point
  def initialize(x = 0, y = 0)
    @x = x
    @y = y
  end
  attr_accessor :x, :y
  
  def o_point_symmetry!
    @x = -@x
    @y = -@y
  end
end

pt = Point.new(50, 100)
pt.o_point_symmetry!
p pt   #=> #<Point:*** @x=-50, @y=-100>


逆に言えば、インスタンス変数に代入するとインスタンスメソッドは破壊的になってしまう。なお、o_point_symmetry!メソッド内の @x, @y は、レシーバーを表す self を使った

class Point
  def o_point_symmetry!
    self.x = -self.x
    self.x = -self.y
  end
end

と同じことである。ここいらがよくわかっていなかったところなのだよなあ。

なお、Pointクラスのオブジェクト pt を、self = pt のように直接 self に代入することはできない。上で replaceメソッドを作ったのは、そのためである。このメソッドは、self への代入のように働く。


それから、破壊的メソッドとは関係ないけれど、クラスにインスタンスメソッド to_s を作っておくと、puts で出力したときに、自分の好みの形でオブジェクトが出力できる。デバッグに便利らしい。

class Point
  def to_s
    "point(#{@x}, #{@y})"
  end
end

pt = Point.new(50, 100)
puts pt    #=> "point(50, 100)"

オブジェクトの変数展開までできる。暗黙に to_sメソッドが呼ばれるようだ。

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

pt = Point.new(50, 100)
puts "#{pt}"    #=> "点(50, 100)"


追記(12/12)

破壊的メソッドと破壊的でないメソッドを作り分ける。

class A
  def initialize(n)
    @a = n
  end
  attr_reader :a
  
  def add!(num)
    @a += num
    self
  end 
  
  def add(num)
    A.new(@a + num)
  end
end

p x = A.new(1)  #=><A:0x007fd426e4d548 @a=1>
p x.add!(10)    #=><A:0x007fd426e4d548 @a=11>
p x             #=><A:0x007fd426e4d548 @a=11>
p x.add(20)     #=><A:0x007fd426e4d250 @a=31>
p x             #=><A:0x007fd426e4d548 @a=11>