❤️

Rubyでメタプログラミングをやってみた

2024/06/14に公開

はじめに

最近メタプログラミングという言葉を聞きました。なんとなく分かっているつもりではいましたが、実際にメタプログラミングを書いたことはなく、ふわっとしか理解していなかったので実際にメタプログラミングをやってみて理解を深めてみました。

メタプログラミングとは

wikipediaより引用

メタプログラミング (metaprogramming) とはプログラミング技法の一種で、ロジックを直接コーディングするのではなく、あるパターンをもったロジックを生成する高位ロジックによってプログラミングを行う方法、またその高位ロジックを定義する方法のこと。主に対象言語に埋め込まれたマクロ言語によって行われる。

とありますが、私はメソッドを動的に作るロジックと解釈しました。
ChatGPTに『メタプログラミングはメソッドを動的に作るロジックですか?』と尋ねてみたところ正しくはあるものの、それだけだと足りないと指摘されました。

↓GPT産

メタプログラミングはそれだけに限らず、プログラムそのものを動的に生成、変更、または拡張する技法全般を指します。

  • クラスやモジュールの動的定義
  • メソッドのオーバーライドやエイリアス
  • メソッドミッシング
  • DSL(ドメイン固有言語)の作成

メソッドの動的生成以外にも上記などがメタプログラミングになるそうです。
全てやると頭がパンクするので、今回の記事ではメソッドの動的生成を試してみたいと思います。

実装

今回はRubyのattr_accessorの機能を自前で作ってみようと思います。attr_accessorとは、外部からクラス内の変数を参照するためのゲッターメソッドとその変数を変更するためのセッターメソッドを定義するものです。

attr_accessorを使わないと外部から参照できない

class Hoge
  def initialize(name)
    @name = name
  end
end

hoge = Hoge.new('山田哲人')
puts hoge.name # attr_accessorでnameを定義していないのでエラーになる

attr_accessorがあるので変数を外部から参照できる

class Hoge
  attr_accessor :name

  def initialize(name)
    @name = name
  end
end

hoge = Hoge.new('山田哲人')
puts hoge.name #=>山田哲人

このattr_accessorの機能をメタプログラミングで自前で作ってみます。

class Hoge
  def self.my_attr_accessor(*attrs)
    attrs.each do |attr|
      define_method(attr) do
        instance_variable_get("@#{attr}")
      end

      define_method("#{attr}=") do |value|
        instance_variable_set("@#{attr}", value)
      end
    end
  end

  my_attr_accessor :name
end

hoge = Hoge.new
hoge.name = "山田哲人"

puts hoge.name  #=> "山田哲人"

define_methodを使って、属性名に対応するゲッターメソッドを動的に定義します。このメソッドは、インスタンス変数@attrの値を返します。

define_method(attr) do
  instance_variable_get("@#{attr}")
end

define_methodを使って、属性名に対応するセッターメソッドを動的に定義します。このメソッドは、インスタンス変数@attrに引数valueを設定します。

define_method("#{attr}=") do |value|
  instance_variable_set("@#{attr}", value)
end

my_attr_accessorがattr_accessorの役割を果たしているので、ゲッターセッターは個別に定義していませんが、メタプログラミングによって動的に定義することができました。

最後に

メタプログラミングは、コードの再利用性を高め、開発をより効率的にするための強力な技法です。今回の例では、Rubyのattr_accessor機能を自作することで、メソッドを動的に生成するプロセスを理解しました。これにより、同様のパターンが複数のクラスで必要な場合に、一度の定義で済ませることができます。

メタプログラミングを学ぶことで、Rubyをさらに深く理解し、柔軟でパワフルなコードを書くことが可能になります。しかし、その強力さゆえに、過度に使用するとコードの可読性が低下し、デバッグが難しくなることもあります。適切なバランスを見つけることが重要です。

Discussion