search
LoginSignup
0
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

Ruby その2 Advent Calendar 2020 Day 20

posted at

updated at

Rubyでは任意の名前でメソッドを定義できる

この記事は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メソッドを使うと、任意のメソッド名でメソッドを定義できる、という話でした。
あまり使うことのないテクニックだと思いますが、覚えておくとどこかで役に立つかもしれません。

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
What you can do with signing up
0
Help us understand the problem. What are the problem?