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

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

メタプログラミングRuby イディオム・黒魔術一覧

概要

書籍「メタプログラミングRuby」 イディオム・黒魔術一覧

詳細

イディオム
項目 概要 URL
ミミックメソッド あたかも言語の組み込み機能であるかのように擬態するメソッド http://d.hatena.ne.jp/tbpg/20130613/1371142971
nilガード nil以外ならそのまま値を設定。nilの場合に任意の初期化値の指定 http://d.hatena.ne.jp/tbpg/20130702/1372775693
名前付き引数 引数の指定順序に依存しないようにメソッドを定義する http://d.hatena.ne.jp/tbpg/20130702/1372778621
自己yield ブロックで自身をyieldすることによって簡潔な記述でメソッドのパラメータ一括設定等が可能になる http://d.hatena.ne.jp/tbpg/20130707/1373205738
Symbol.to_proc ワンコールブロックを簡潔に記載する手法 http://d.hatena.ne.jp/tbpg/20130425/1366910843
魔術
項目 概要 URL
配列引数 任意の数の引数を配列として取得する http://d.hatena.ne.jp/tbpg/20130707/1373209918
アラウンドエイリアス 再定義したメソッドから以前のメソッドエイリアスとして呼び出す http://d.hatena.ne.jp/tbpg/20130722/1374504386
ブランクスレート メソッドを非定義にする http://d.hatena.ne.jp/tbpg/20130804/1375631901
クラス拡張 クラスの特異クラスにモジュールをインクルードして、クラスメソッドを定義する。 http://d.hatena.ne.jp/tbpg/20130804/1375632314
クラス拡張ミックスイン RailsActiveSupport::Concernそのもの。
Moduleのinclude時にクラスメソッドを拡張する
http://d.hatena.ne.jp/tbpg/20130714/1373806263
クラスインスタンス変数 クラスの状態をクラスオブジェクトのインスタンス変数に格納する http://d.hatena.ne.jp/tbpg/20130808/1375978384
クラスマクロ クラス定義内でクラスメソッドを利用すること http://d.hatena.ne.jp/tbpg/20130808/1375980547
クリーンルーム ブロックを評価する環境としてオブジェクトのinstance_evalを利用します。 http://d.hatena.ne.jp/tbpg/20130810/1376155894
コードプロセッサー コードプロセッサーとは外部ソースにあるコード文字列を処理する手法です。 http://d.hatena.ne.jp/tbpg/20130814/1376493025
コンテキスト探査機 オブジェクトのコンテキストにある情報にアクセスします http://d.hatena.ne.jp/tbpg/20130814/1376493497
遅延評価 Procやlambdaにコンテキストを保管して後から評価します。 http://d.hatena.ne.jp/tbpg/20130815/1376586348
動的ディスパッチ 実行時に呼び出すメソッドを決めます。 http://d.hatena.ne.jp/tbpg/20130815/1376587840
動的メソッド 実行時に呼び出すメソッドを決めます。 http://d.hatena.ne.jp/tbpg/20130816/1376666665
動的プロキシ どのメソッドにも当てはまらないメッセージを他のオブジェクトに転送します。 http://d.hatena.ne.jp/tbpg/20130818/1376839480
フラットスコープ クロージャーを使って2つのスコープで変数を共有する。 http://d.hatena.ne.jp/tbpg/20130820/1377003702
ゴーストメソッド 実行時にメソッドを作成します。 http://d.hatena.ne.jp/tbpg/20130226/1361883982
フックメソッド メソッドをオーバーライドしてオブジェクトモデルのイベントを補足する。 http://d.hatena.ne.jp/tbpg/20130824/1377357975
カーネルメソッド カーネルモジュールにメソッドを追加することで、
すべてのオブジェクトで利用可能なメソッドを定義する
http://d.hatena.ne.jp/tbpg/20130825/1377432092
遅延インスタンス 初回アクセスまでインスタンス変数を初期化しない。 http://d.hatena.ne.jp/tbpg/20130825/1377433271
ミミックメソッド あたかも言語の組み込み機能であるかのように擬態するメソッド http://d.hatena.ne.jp/tbpg/20130613/1371142971
モンキーパッチ 既存のクラスの振る舞いを変更する。 http://d.hatena.ne.jp/tbpg/20130825/1377434535
名前付き引数 メソッドの引数をまとめてハッシュに入れて、名前で識別可能にする。
※Ruby2では言語機能に組み込まれている
--
ネームスペース 定数(クラス名も定数の一種)をモジュール内に定義して名前の衝突を避ける --
nilガード nil以外ならそのまま値を設定。nilの場合に任意の初期化値の指定 http://d.hatena.ne.jp/tbpg/20130702/1372775693
オブジェクト拡張 オブジェクトの特異クラスにモジュールをインクルードして特異メソッドを定義 http://d.hatena.ne.jp/tbpg/20130825/1377435190
オープンクラス 既存のクラスを拡張する --
パターンディスパッチ 名前を元にして呼び出すメソッドを選択する。 http://d.hatena.ne.jp/tbpg/20130825/1377436601
サンドボックス 信頼出来ないコードを安全な環境で実行する。 http://d.hatena.ne.jp/tbpg/20130825/1377437442
スコープゲート Rubyのプログラムはclass,module,defのキーワード前後でスコープが切り替わる --
自己yield ブロックで自身をyieldすることによって簡潔な記述でメソッドのパラメータ一括設定等が可能になる http://d.hatena.ne.jp/tbpg/20130707/1373205738
共有スコープ フラットスコープ内で複数メソッドを定義することで変数を共有する http://d.hatena.ne.jp/tbpg/20130827/1377623484
特異メソッド 特定のオブジェクトにメソッドを定義 http://d.hatena.ne.jp/tbpg/20120507/1336405770
コード文字列 文字列のRubyコードを評価する。 http://d.hatena.ne.jp/tbpg/20130901/1378049472
SymbolのProc変換 シンボルを一つのメソッドを呼び出すブロックに変換する http://d.hatena.ne.jp/tbpg/20130425/1366910843
番外編

書籍「メタプログラミングRuby」とは関係ないが、メタプログラミン・DSL・リフレクション等との関連が深い
手法について下記にまとめます。

項目 概要 URL
ミミックスペース 全角スペースによるメソッド作成 http://d.hatena.ne.jp/tbpg/20130807/1375887096
Ghostable ゴーストメソッド作成支援モジュール http://d.hatena.ne.jp/tbpg/20130815/1376584674
文字列クラス クラス名文字列からクラスのインスタンスを取得する http://d.hatena.ne.jp/tbpg/20130826/1377524044
『!!』(double not) !!を2つ重ねることによって、論理型の返却が必要な際にnilをfalseに変換しつつ真偽判定を行う http://d.hatena.ne.jp/tbpg/20140113/1389614884
メタなコードの補助コメント メタなコードを書く際に完成予想図をコメントに書く http://d.hatena.ne.jp/tbpg/20140129/1391001713