Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

メタプログラミングRuby | イディオム | ミミックメソッド

概要

組み込み構文のように擬態するミミックメソッド

内容

Rubyメソッドには記号を利用できたり、括弧を省略することで
あたかも言語に組み込まれた構文のように記述できます。
このようなテクニックをミミックメソッドと呼びます。
ミミック=擬態。ドラクエ世代には分かりやすいですね。

ただのメソッドが組み込みメソッドに見えるケース

putsはあたかも言語の構文のように見えますが、Kernelのメソッドです。

puts "hoge"

Kernel.puts("hoge")

Useクラスを継承しているようにみえるケース

実際はUseメソッドが返却するクラスを継承している

# encoding: UTF-8
require 'pp'

class Hoge
  def say
    puts "hoge"
  end
end

class Hage
  def say
    puts "hage"
  end
end

class Hige
  def say
    puts "hige"
  end
end

def Use(class_name)
  puts class_name
  return eval("#{class_name}")
end

class ChildHoge < Use 'Hoge'
end
ChildHoge.new.say

class ChildHage < Use 'Hage'
end
ChildHage.new.say

class ChildHige < Use 'Hige'
end
ChildHige.new.say

出力

Hoge
hoge
Hage
hage
Hige
hige

構文を曲げてサブルーチンを実装可能にしてみる

# encoding: UTF-8
require './my_kernel'

Sub hoge(param) {|p|
  puts "hoge_" + p
}

hoge("test")

出力
きちんと動作もします。

hoge_test

VBのサブルーチンライクな宣言をしています。
実際のところはSub,hoge,paramはすべてメソッドとして認識されています。
method_missingでそれらをKernelのフィールドに保存しておき、Subが見つかった時点で
保存しておいたフィールドを元にKernelのインスタンスメソッドを動的に定義しています。

以下、動的定義部分のコード

# encoding: UTF-8

module Kernel
  attr_accessor :sub_factors, :sub_block

  def method_missing(method_name, *args, &block)
          puts "aaa:#{method_name.to_s}"
          puts "bbb:#{args.to_s}"

    if method_name == :Sub
sub_code =<<EOS
def #{@sub_factors[1].to_s}(#{@sub_factors[0].to_s})
  @sub_block.call #{@sub_factors[0].to_s}
end
EOS

      eval(sub_code, binding)
    else
      @sub_factors ||= []
      @sub_factors << method_name
      @sub_block = block
    end
  end
end

※あくまで、こんなことが出来る、という話で実用レベルのコードではありません