・メタプログラミングって何?
・なんでRubyとメタプログラミングが相性がいいの?
・メタプログラミングで何ができるの?
最近、Rubyのシルバー試験も合格できRubyの魔術書も読んだ上で、、、Rubyの便利さや凄さに驚いてます。
Rubyの魅力を語るにあたり、メタプログラミングは外せません。
・・・が、そもそもメタプログラミングって何?なんでそれが出来ると嬉しいの?ってのが当時の私はよく理解してませんでした。
過去の私に向けて、Rubyとメタプログラミングについて記載しておこうと思います。
*「Rubyの魔術書」言ってるのはこの本です。
メタプログラミングとは
メタプログラミングとは、「コードを記述するコードを記述することである」と魔術書には書いてました。
最初は、「?」ですが読み進めていただけるとなんとなく掴めると思います。
では行ってみましょう!
イントロスペクション
まずはメタプログラミングに欠かせない機能の1つであるイントロスペクションからご説明。
コード実行時に、対象物(オブジェクト)の中身をあれやこれやと調査できる機能です。
Rubyはコレが可能となってます。
- 私が属するクラスはXXXですよ
- 私に定義されているインスタンスメソッドの一覧はコレですよ
- インスタンス変数の一覧はコレです
コレだけでも結構便利です。特にテストクラスを記述したり、デバックする時にはめちゃくちゃ便利!
(メソッドについては後述)
メタプログラミングの代表例。Active Recordライブラリ。
次に、実際にメタプログラミングを使ってるライブラリで詳細を説明しますね。
代表的なのが、Active Recoredライブラリ。Ruby on Railsでも使うライブラリです。
オブジェクトとテーブルのマッピングを行ってくれる有名なライブラリになります。
これの何がすごいか、、、例えば本に関するテーブルを作成します。
テーブルには、titleカラムがあるだけとします。
その後に、、、
class Book < Active Record:Base
end
コレ!コレだけ書くだけで、Bookテーブルのカラムのgetter/setterが自動で作成されます。
つまりどういうことかと言うと、
book = Book.create
book.title = "Rubyの魔術書"
puts book.title #> Rubyの魔術書
このように、getter/setterのメソッドを作らなくても自動でカラムに関連したgetter/setterのメソッドがActive Recoredライブラリ側で自動作成されるので、わざわざ自分でメソッドを作らなくていいわけです。
詳細を説明します。
プログラムの実行中に、Active Recordライブラリがイントロスペクションを使って、Bookテーブルを取得。
さらにそのテーブルにあるtitleカラム取得。
取得したtitleカラムに関するgetter/setterなどのアクセサメソッドを定義。
つまり、Active Recordライブラリの内部でコードを書く(アクセサメソッドを定義)コードを内部に記述しておりそれがプログラム実行中に動いて勝手にメソッドを定義してくれる訳です。
これの何がすごいって、動的にメソッドを定義できることです。
Active Recordライブラリのルールに基づいたクラスさえ作ってしまえばどんなテーブルだろうと、カラムが何で何個あろうとも勝手にメソッドを定義してくれるわけです。
テーブルやカラムが増減する度にソースコードを改修する必要がないのはかなり嬉しい!
メタプログラミングについてなんとなく理解できてきたでしょうか?
メタプログラミングの光と闇
かっこいいタイトルにしちゃいましたw
まずは光の部分。
Ruby on Railsが結構初心者にも優しいよって言われている理由が、正にActive Recordライブラリのようなメタプログラミングの助けがあるからなんだと思います。
特にガリガリ実装しなくても、感覚的にプログラミングが出来るから。
じゃあ、闇の部分は?
中身がわかりづらいんですよね。特に中級者くらいになってくるとメソッドを定義しているところがどこに記載してあるのかはっきり分からないと不安で先に進めなくなる。
後、バグが発生してしまうと気がつきにくかったりします。
とは言っても、メタプログラミングは理解してさえしまえば強力なのは事実です。
「大いなる力には大いなる責任が伴う」ってやつです。
他の言語は?
C言語とか、Javaとかでもメタプログラミングできるのって話です。
C言語は、そもそもイントロスペクションが使えないです
Javaは一部使えるそうですが、コンパイルが必要。。。
一度コードをまとめて機械語に置き換えてから実行しないといけないわけです。
そのため、C言語やJavaはメタプログラミングに向いてません。
Rubyは、前述したようにイントロスペクションは使える上に、インタプリタ言語です。
(コンパイラは必要とせず逐一機械語に翻訳する。)
つまり、Rubyはメタプログラミングに向いている(コードを書くコードが書ける)と言うわけです
メタプログラミングに使えるメソッドってどんなのがあるの?
メタプログラミングに使えるメソッドについて少しだけ紹介します。
eval
文字列をRubyプログラムとして実行します。
以下の例では、文字列でhogeメソッドを定義してevalでRubyプログラムとして実行してます。
例では、大したことしてないですが文字列をメソッドとして実行できるのはかなり強力です。
強力すぎて悪用されかねないので使用注意のメソッドです。
(sqlインジェクションとか、ハッキングされる元になるので仕事では可能な限り避けましょう)
eval("def hoge(a,b); p a + b end")
hoge(2,4)
#=> 6
instance_methods(false)
インスタンスメソッドの一覧を取得できます。
falseをつけることで、継承したメソッドを外すことができます。
instance_variables
インスタンス変数を取得できます。
メタプログラミングは禁断の果実。使い方を誤るとこうなる。。。
メタプログラミングはかなり強力です。
誤った使い方をするとどんなことが起きるのか何個か例を上げます。
- privateメソッドを無理やり呼ぶことも出来てしまう
- メソッドなどの再定義も可能。なので、既存のメソッドであるStringメソッドなんかも上書き更新できてしまう
- ゴーストメソッド(method_missing())を使って無限エラーに陥る
などなど。
強力な分、リスクもあります。
メタプログラミングはどんな時に使えばいいの?
ここまでメタプログラミングの説明をしておいてなんですが、、
メタプログラミングは最初から使わない方向で考えておいた方がいいです。
プログラミングする上で一番なのは、「コードをシンプルに保つこと」。
コレだけです。
じゃあ、どんな時にメタプログラミング使うの?ですが、
コードがシンプルじゃなくなった時。つまりコードが複雑化したときや重複が多くなった時です。
その時になってからメタプログラミングを採用するのがベストだと「魔術書」には書いてましたw
流れとしては、
- 問題を解決するためのシンプルなコードを書いてみる
- 解決した後にコードを眺めて、複雑だったり、重複が多そうだったらメタプログラミングを考える
みたいな感じです。1.で一度解決しておけばテストコードも記載できますからね。
後からメタプログラミングでリファクタリングしてもテストコードがしっかり書かれていればバグる心配もないはずです。
まとめ
- Rubyはメタプログラミングに向いている
- メタプログラミングは強力だけど、リスクもある
- 最初はシンプルなコードから。コードが複雑化したり、重複が多くなった時にメタプログラミングを使う
メタプログラミングと言う概念を知っているだけでも達人に近づけたはずです!
いえ、近づけたんです!
この記事を読んだ人が、Rubyの達人になれることを祈ってます!