0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Ruby】refinement でクラスメソッドを定義するには

Posted at

Ruby の refinement は,クラスやモジュールの改変(メソッドの追加・修正など)を限定されたスコープ内でのみ有効にする仕組みだ。

これを使って Array クラスにクラスメソッドを追加しようとしたが,その方法に迷い,最終的に解決したので記事にした。

refinement とは

refinement をご存知の方は飛ばして次節へどうぞ。

たとえば,Array クラスに,「要素(数値)の平均値を返すメソッド average」を追加したいとする。
refinement を使わない場合,

class Array
  def average
    sum.fdiv(length)
  end
end

となるだろう。
しかし組込みクラスの変更は思わぬところに影響を与えうるので,たいへん危なっかしい1

Array#average を MyAwesomeClass2 の中だけで使いたいなら,refinement を使って以下のように書ける。

module AddAverageToArray # ← モジュール名は何でもいい
  refine Array do
    def average
      sum.fdiv(length)
    end
  end
end

class MyAwesomeClass
  # ここでは Array#average は使えない

  using AddAverageToArray
  # ここ以降,Array#average が使える
  # ただし,このクラス定義式の中だけ
end

試行錯誤

この節では私が試行錯誤した過程を書く。やり方だけ知りたい方は次節へどうぞ。

以下のような馬鹿馬鹿しいクラスメソッドを,refinement で実現したいとしよう。

class Array
  def self.speak
    puts "あれぇ"
  end
end

def self.speak で

最初に試したのは以下のようなやり方。

module AddSpeakToArray
  refine Array do
    def self.speak
      puts "あれぇ"
    end
  end
end

クラス定義式中でクラスメソッドを定義するのに,def self.speak みたいに書くのはごく普通だよね。

ところが使おうとすると

using AddSpeakToArray

Array.speak
# => undefined method 'speak' for class Array (NoMethodError)

なんと,Array.speak が定義されていない。
どゆこと?

もしかして,refine Array のブロック内の selfArray じゃないってこと?
確かめよう:

module AddSpeakToArray
  refine Array do
    p self
    # => #<refinement:Array@AddSpeakToArray>

    p self.class
    # => Refinement
  end
end

そうだったのか。
Array じゃなくて,Refinement というクラスのオブジェクトだった。
このクラスを直接どうこうした経験がなく,存在すら知らんかった。

def Array.speak で

クラスメソッドを定義する方法はいくつもある。
refine Array のブロック内で selfArray ではない,ということなら,self でなく Array と書けばいいじゃん。

module AddSpeakToArray
  refine Array do
    def Array.speak
      puts "あれぇ"
    end
  end
end

実際,これで使えた:

using AddSpeakToArray

Array.speak
# => あれぇ

やったー,解決!

……とはならんのよ。

念のため有効範囲が限定されていることを確認しようと,using AddSpeakToArray を消してみた。
それでも Array.speak が使えてしまう!

特異クラスを refine

最後に試したのが,Array の特異クラスを refine する方法。

以下のことを思い出そう。

  • Array のクラスメソッドとは,Array の特異メソッドに他ならない。
  • オブジェクト x の特異メソッドは,x の特異クラスのインスタンスメソッドである。
  • オブジェクト x の特異クラスは x.singleton_class で得られる。

これを踏まえれば以下のように書ける。

module AddSpeakToArray
  refine Array.singleton_class do
    def speak
      puts "あれぇ"
    end
  end
end

これだと,using AddSpeakToArray したときのみ Array#speak が使える。
解決だ。

  1. たとえば,二つのライブラリーが同じクラスに同名のメソッドを追加し,仕様が異なる,といった状況を想像してみよう。

  2. このクラス名に意味はない。awesome という英単語は「すげぇ」という意味らしい。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?