オモンパカリスト

深層学習、計算論的神経科学に興味あります

メタプログラミングRubyを2章まで読んだら僕の場合はRuby超おもしれ〜!って感想になった。いまんとこ。

きっかけ

 Rails TutorialでRailsの開発を学んでいる。

Railsのすっげー魔法みたいな記述を教わって、 チュートリアルの言われる通り書いてるとあら不思議、ミニTwitterが作れる。

でも、Rubyという言語のおかげで成せるこの魔法みたいな記述は 今後違うものを開発するときに応用できる自信がない。

なにがどーなってるのかちょっとよくわからないままなのだ。抽象的な感覚のみが残っている。

そんな僕みたいなせっかちが語り出しちゃったなんとも青二才らしい記事がちょうどいま軽くバズってる。

sorehito.xyz

 そもそも僕は他の言語でバリバリ書けてたわけじゃない。

だから、オブジェクト指向言語についても継承だとか、インスタンス生成だとか、メソッドを呼び出すとか、 よくありがちな簡単な認識程度のレベル。

そこで、プログラミング言語Rubyは、一体どういったことを裏でやってくれてるのか、 Railsの魔法のような記述はいったいどういった仕組みなのか、そういったものに強く知りたくなった。

きっかけその2

 そんなとき、それを形容するような、とある言葉にであった。

メタプログラミングである。

Rubyで調べてたら、しょっちゅうでてくるよくわかんない言葉。

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

Wikipediaの記事から引っ張ってきた。よく意味がわからぬ。

ある程度他のサイトも探ってみたけど、これといって理解できずに本屋に飛び出した。

っていうのはちょっと嘘で、大学の研究のためにディープラーニングの本を買いにいったついででした。

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習 (機械学習プロフェッショナルシリーズ)

ディープラーニングの書籍学習も、このブログでやっていきたい所存。

とまぁ、話を戻して、Rubyのコーナーにある一つの本に惹かれて、ついでに購入した。

メタプログラミングの世界に足をつっこむことになった。

読んでる本

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

原書そのものがフランクな文章なんだろう、日本語訳もめっちゃフランク。

ちょろっと抜粋してみると

Rubyのコードは常にオブジェクトの内部で実行されるので、Kernelモジュールのメソッドはどこからでも呼び出せる。 おかげでメソッドのprintが言語のキーワードのように見えるわけだ。 カッコよくない?

ふふっ、てなった。

ちなみにこの第二版はRuby2系、Rails4系になって書き下ろしたもので、 第一版はRuby1.8系のころのものだった。Amazonのレビューの評価も高いのも頷ける。 触れてみて今のところわかりやすい本だと思っている。

読むにあたってのスキルレベル

 メタプログラミング=中級者以上。なんてイメージがあった。

でもこの本は多分、プログラミング言語Rubyを選びました!ガシガシ勉強します!っていう初心者でもついていけると思う。

パーフェクトRubyをある程度読み方わかるよ、って人ならいける。自分がそう。

この本でもいってるのが、配列の要素を繰り返し取得したいコードを書くときに、

「配列.each(Rubyのマニュアルの記法に従えばArray#eachメソッド)を使うことを思いつくあなたは大丈夫。

for文でまわす、なんてことを思いついちゃった Ruby初心者すぎるあなた。 それでも大丈夫」(実際はこんな書き方はしてない)

っていってるくらいです。ついていける。買っちゃえ。

とかいってまだ自分も第3章の途中です。これから詰むと思います。

とにかく面白いです

さっきのバズってる初心者さん(同志)にオススメしたい本でもあります。

俺は本物のプログラミングが書きたいんだよ!!って感じならこれ読んでRubyを好きになれると思います。

Rubyがどうやって動いてるのか、そんな仕組みに著者本人が関心して興奮しながらもこの本を書いているのが読んでいてヒシヒシと伝わります。

めちゃくちゃ面白いんです。いまんとこ。

どういう構成か

第1章でメタプログラミングとはなにか、背景を含めて紹介してくれています。 本書では何回もメタプログラミングの言い回しを改めて定義したりします。

メタプログラミングとは、コードを記述するコードを記述することである。」

この言い回しから始まります。

本の最後では メタプログラミングというものは存在しない。」 で締めくくってます。

なんじゃそりゃ、って感じですけど。

第2章からそのメタプログラミングの真の姿を、化けの皮をはがすべく、 Rubyが用意した設計を理解していくことになります。

そしてなんともわかりやすいことに、二人のプログラマの登場人物が一緒に冒険してくれます。

一人は、えっと、あなた自身。あ、僕ですか。まじか。

もう一人は、熟練プログラマのビル。(ちょっと口悪いキャラなのがユニーク)

OJTみたいな感じで、新人エンジニアのあなたとビルが様々な問題に直面します。

その所々でビルが嬉しそうに講義を始めます。聞いてあげましょう。

本題です。

 2章ではRubyならではのオープンクラス、その問題点やオブジェクトモデル、メソッド探索・実行についてお勉強します。 というわけで僕が2章を読んで学んだことを、備忘録がてら徒然なるままに書いていきます。

非常に見づらいと思うし、自己満足の備忘録ですので、ご容赦ください。

オープンクラスとは

class D
  def x; 'x'; end
end

class D
  def y; 'y'; end
end

obj = D.new
obj.x # => "x"
obj.y # => "y"

最初のクラス定義でDを定義しているので、二回目は再定義せずに、 既存のクラスを再度オープンしてyメソッドを追加している。

これがオープンクラスという技法

これがいつでもできるわけだから、標準クラスのArrayやStringにもできちゃう。

問題点:モンキーパッチ

オープンクラス時に、すでに同じメソッド名があるのをもう一回追加しちゃうこと。

例えば先ほどに追加で、もう一度

class D
  def x;'xx';end
end

こうなったら同名のメソッドxをオーバーライド(上書き)することになる。 特に標準クラスにメソッドを追加するなら元に同名のメソッドがないかしっかり確認するのが大事。

[].methods.grep /^reverse/ # => [:reverse_each, :reverse, :reverse!]

ついでにこれは、Arrayクラスのメソッドを取得し、Array#grepメソッド正規表現でreverseから始まる要素をマッチさせるっていう、 単純明快なメソッドチェーンの例。

クラスへの安易なパッチをモンキーパッチという。

インスタンス変数とメソッドの居場所

オブジェクトはクラスのインスタンス

オブジェクトのほうに、インスタンス変数が保有される。

ていうことは、同じクラスの異なるオブジェクト達が保有しているインスタンス変数の値は異なる(ことができる)。

メソッドは、クラスにいる。

これがメソッド呼び出しの裏でRubyがどうしているのか理解していく上で重要。

※本書では、定数のパスやそれぞれの要素のクラスについてだとか、このあと勉強していきますが今回は端折ります。

メソッド呼び出しの実態に迫る

Rubyではメソッド呼び出しを2つの手順で働く。

class MyClass
  def my_method; 'hello!'; end
end

class MySubclass < MyClass
end

obj = MySubclass.new
obj.my_method # => "hello!"

最後の行がメソッド呼び出し。メソッドの探索を開始する。

  1. 対象メソッドのレシーバ(この例ではobj)のクラスを調べる。(右に一歩)
  2. クラスに定義されたインスタンスメソッドのなかで対象メソッドがないか探索する。
  3. なければそのスーパークラス(親クラス)にうつる。2に戻り繰り返す。(それから上へ)

つまりこの例では、レシーバobjのクラス、MySubclassの中身を探索する。

でもここにはなかったので、スーパークラスのMyClassに移る。

my_method発見!実行。普通だと最後の行の評価が戻り値となる。

個人的に、この本を片手にターミナルを開いてirb(僕の場合は上位互換のpry)を叩いたりして 理解を深めている。

pry(main)> obj.class
=> MySubclass
pry(main)> obj.class.superclass
=> MyClass
pry(main)> obj.methods.grep(/^my_method$/)
=> [:my_method]
pry(main)> MySubclass.instance_methods.grep(/^my_method$/)
=> [:my_method]
pry(main)> MySubclass.instance_methods(false).grep(/^my_method$/)
=> []
pry(main)> MyClass.instance_methods(false).grep(/^my_method$/)
=> [:my_method]

Kernel#classメソッドはそいつのクラスを返す。

次にobj.classの返り値MySubclassをそのままレシーバにしてClass.superclassメソッドを実行している。

これがメソッド複数繋げて書けるっていう噂のメソッドチェーン。強力な文法だなぁ...

ちなみにクラスの継承チェーンは

pry(main)> MySubclass.ancestors
=> [MySubclass, MyClass, Object, PP::ObjectMixin, Kernel, BasicObject]

こんな感じで取得できる。

PP::ObjectMixinが混じってる... pryで読み込んでいるからっぽいな

Kernel#methodsはオブジェクトに着目している。配列で返す。

Module.instance_methodsはそのクラスが保持しているインスタンスメソッドを配列で返す。

falseを引数に与えると継承してきたメソッドは無視できる。

配列で返ってきたのをレシーバにしてArray#grepを用いている。

そんなこんなで

本書ではこの後もModuleのこととか、importのこととか、レシーバをselfで保持することだとか、

かなりわかりやすく大事なことを教えてくれます。

また、こういった説明のサイドストーリーとして新人のあなたとビルが自分たちの会社が開発した 蔵書管理システムのリファクタリングに挑戦していて、実践的な知識が身につきます。

Rubyでどういったことができるのか、そんな面白さの発見ができるし、

有名なgemのソースコードを実際に覗いて解説したり、Matzを筆頭に先人達の知識の結晶を味わえる、

めちゃくちゃ興奮できる本だと思います。

プログラミングの世界に踏み込みたい!そんな人にこそ、特におすすめしたい本でした。

ちなみに三章あたりから早速ついてくの大変になってきました(笑)

三章は動的メソッドやゴーストメソッドを扱い、これがまたメタいコードが書ける技術になります。

難しいけど面白いです。またかきます。