この記事はRuby その2 Advent Calendar 2020 の20日目の記事です。
https://qiita.com/advent-calendar/2020/ruby2
Rubyでは任意の名前でメソッドを定義できます。
……と言うと、「いやいや、def $%^&*
とか書けないじゃないか」「||
は再定義できない演算子じゃないか」といった声が聞こえてきそうです。これはどちらも正しいのですが、それでいてRubyでは任意の名前でメソッドを定義できます。
ではそれはどういうことなのか、詳しく見ていきましょう。
def
構文
Rubyでメソッドを定義するにはdef
構文を使います。
# fooメソッドの定義
def foo
end
# メソッド名の後ろには!や?をつけられる
def foo!
end
def foo?
end
# =終わりのメソッド名を定義するとセッターのように使える
def foo=(value)
end
# 大文字もok
def FOO
end
# ひらがななどもok
def ほげ
end
# キーワードもok
def def
end
# 一部の演算子は再定義できる
class Integer
# 42 - 3 が 42 + 3 に化ける!
def -(right)
self + right
end
# 単項+メソッドの定義
def +@
end
end
そして、冒頭の例に挙げたコードはどちらもSyntax Errorとなります。
# Syntax Error
def $%^&*
end
# Syntax Error
def ||
end
ところが、これらの名前でメソッドを定義する方法があります。それはdefine_method
メソッドです。
define_method
メソッド
まずは簡単にdefine_method
メソッドの説明をしましょう。
define_method
メソッドは、その名の通りメソッドを定義するためのメソッドです。
メソッド名を動的に組み立てたり、同じようなメソッドを複数同時に定義したりするためによく使うでしょう。
`name`を大文字にしたメソッド名を定義
define_method(name.upcase) {}
# foo, bar, baz を一度に定義
%i[foo bar baz].each do |name|
define_method(name) { name }
end
任意の名前でメソッドを定義する
そしてdefine_method
は任意の名前を受け付けます。つまり、$%^&*
や||
といった名前のメソッドも定義できます。
これらのメソッドは.
を使ったメソッド呼び出しではSyntax Errorになって呼び出せませんが、__send__
メソッドを使うと呼び出せます。
# メソッドの定義
define_method('$%^&*') { puts 'method name is $%^&*' }
define_method('||') { puts 'method name is ||' }
# 呼び出し
__send__ '$%^&*' # => method name is $%^&*
__send__ '||' # => method name is ||
ですが、たとえ||
メソッドを定義したとしても、||
演算子の挙動が変わるわけではありません。
class FalseClass
# 再定義しても false || something の結果が変わるわけではない
define_method('||') do
false
end
end
p false || true # => true
もちろん次のようないかにも定義できなさそうなメソッド名でも大丈夫です。
define_method('') { puts '空文字列もok' }
__send__ '' # => 空文字列もok
# 0x00から0xffまでのどのバイトでもok
# (class Cの中に入れているのは、Kernel.#p を再定義しないため)
class C
(0x00..0xff).each do |n|
define_method(n.chr) { pp n.chr }
end
end
C.new.__send__ 0x00.chr # => "\x00"
# ランダムなバイト列でメソッドの定義を試しても大丈夫
require 'securerandom'
class C2
10.times do
define_method(SecureRandom.random_bytes(10)) do
end
end
end
p C2.instance_methods(false)
# => [:"4(c\xF4\xBDQ\xD7\xB6\xA0l", :"J&q\xEEZ\x0E\xE3\x9F\"\xC1", :"}\x9F\xCF\x1C\x90\x06o\xFD\x84\xD6", :"U\x8Bc02'\xB4\xF0\xA0\xC4", :"\xEE\x88v\x14\xBA&j\xBE<\x10", :"\a+,\xCD\"\xF4\x83\x80\xE5\xCA", :"4\xC8\xAB/2\x06\f\xF1\xFB\xFA", :"\xBA\xE2E;\xCE\xBC\xB8\xB0b\xD0", :"u [l^q%\xC9\x03\xA2", :"q\xC5O\xAF\xDB\x05t\x86!\x88"]
まとめ
Rubyではdefine_method
メソッドを使うと、任意のメソッド名でメソッドを定義できる、という話でした。
あまり使うことのないテクニックだと思いますが、覚えておくとどこかで役に立つかもしれません。