メタプロRuby読む

irb(main):012:0> Greeting.new("hello")
=> #<Greeting:0x0000000111c3b628 @text="hello">
irb(main):013:0> obj = Greeting.new("hello")
=> #<Greeting:0x00000001126f2d50 @text="hello">
irb(main):014:0> obj.welcome
=> "hello"
irb(main):015:0> obj.instance_variables
=> [:@text]
irb(main):016:0> obj.class
=> Greeting
irb(main):017:0> obj.class.instance_methods
irb(main):018:0> obj.class.instance_methods(false)
=> [:welcome]
irb(main):019:0>

プログラム実行時に新しいインスタンスメソッドを追加できるのか?

メタプログラミングとは、言語要素を実行時に操作するコードを記述することである
この本において言語要素とは、変数、クラス、メソッドなどの言語の要素のこと。

もっとライトな定義があってそっちの方がわかりやすいかも
メタプログラミングとは、コードを記述するコードを記述することである。

言語要素を操作したいなら、言語要素は実行時に存在しなければいけない。

C言語においては、プログラムは「言語要素が存在するコンパイル時の世界」と「マシンコードが存在する実行時の世界」の2つの異なる世界にまたがっている。

Rubyのコーディングの能力を上げる以上、ライブラリとは付き合えた方が良くて、そうなるとどうしてもメタプロとは付き合う必要がある。

irb(main):001* 3.times do
irb(main):002* class C
irb(main):003* puts "hoge"
irb(main):004* end
irb(main):005> end
hoge
hoge
hoge
=> 3
irb(main):006> C
=> C
irb(main):007> C.class
=> Class
irb(main):008> C.instance_methods
irb(main):009> C.instance_methods(false)
=> []
irb(main):010* class C
irb(main):011* def x
irb(main):012* puts "hoge"
irb(main):013* end
irb(main):014> end
=> :x
irb(main):015> C.instance_methods(false)
=> [:x]
irb(main):016>
class
は同じ名前のクラスを再度定義しない。再オープンしてメソッドを追加しているのがわかる。

irb(main):001> # encoding: utf-8
irb(main):002>
irb(main):003* class Numeric
irb(main):004* def to_money(currency = nil)
irb(main):005* Monetize.from_numeric(self, currency || Money.default_currency)
irb(main):006* end
irb(main):007> end
irb(main):008>
=> :to_money
irb(main):009> require "monetize"
=> true
irb(main):010> 12.to_money("USD")
[WARNING] The default rounding mode will change from `ROUND_HALF_EVEN` to `ROUND_HALF_UP` in the next major release. Set it explicitly using `Money.rounding_mode=` to avoid potential problems.
=> #<Money fractional:1200 currency:USD>
irb(main):011> standard_price = 12.to_money("USD")
=> #<Money fractional:1200 currency:USD>
irb(main):012> standard_price.class.instance_methods(false)
=>
[:as_us_dollar,
:as_ca_dollar,
:as_euro,
:split,
:fractional,
:thousands_separator,
:round_to_nearest_cash_value,
:decimal_mark,
:bank,
:cents,
:to_s,
:symbol,
:to_i,
:to_f,
:round,
:format,
:allocate,
:hash,
:inspect,
:to_money,
:currency,
:dollars,
:currency_as_string=,
:currency_as_string,
:with_currency,
:dup_with,
:exchange_to,
:to_d,
:amount]
irb(main):
irb(main):001> 12.to_money("USD")
(irb):1:in `<main>': undefined method `to_money' for an instance of Integer (NoMethodError)
12.to_money("USD")
^^^^^^^^^
Did you mean? to_enum
from <internal:kernel>:187:in `loop'
from /Users/yuuki_haga/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.1/exe/irb:9:in `<top (required)>'
from /Users/yuuki_haga/.rbenv/versions/3.3.1/bin/irb:25:in `load'
from /Users/yuuki_haga/.rbenv/versions/3.3.1/bin/irb:25:in `<main>'
irb(main):002>

このライブラリで、オープンクラス使ってた。
ライブラリだとオープンクラス使うのか。

irb(main):004> String.methods.grep /^instance/
=>
[:instance_method,
:instance_methods,
:instance_variables,
:instance_variable_get,
:instance_variable_set,
:instance_variable_defined?,
:instance_of?,
:instance_eval,
:instance_exec]
irb(main):005>
こんな感じでクラスのメソッド探せるのは知らんかった。地味に便利

irb(main):007> 1.class.instance_methods(false).grep /^instance/
=> []
irb(main):008> 1.class.instance_methods(false).grep /^z/
=> [:zero?]
こんなのもできる

当初はモンキーパッチは、ルールを無視して実行時にこっそりとコードを変更することから、ゲリラパッチと呼ばれていた。これらのパッチを複数当てると、時折直感に反するような相互作用が生まれることがあり、Zope 2では、交戦中のパッチと呼ばれていた。
モンキーパッチは実行時にコードを変更することなのか。

オープンクラスは、実行時に既存クラスをオープンして機能追加できる機能で、
モンキーパッチは、実行時にコードを変更すること

インスタンス変数は値が代入された時に初めて出現する
irb(main):012* class Hoge
irb(main):013* def x
irb(main):014* @y = 1
irb(main):015* end
irb(main):016> end
=> :x
irb(main):017> hoge = Hoge.new
=> #<Hoge:0x0000000102dd8788>
irb(main):018> hoge.instance_variables
=> []
irb(main):019> hoge.x
=> 1
irb(main):020> hoge.instance_variables
=> [:@y]
irb(main):021>
当たり前っちゃ当たり前だけど、言葉にされると確かになと思う。

同じメソッドであっても、クラスに着目しているときは、インスタンスメソッドと呼び、オブジェクトに注目しているときは、メソッドと呼ぶ
これわかりやすい。クラスについて話をしているなら、インスタンスメソッドと読んだ方が良いし、食らうから生成されたオブジェクトについての話をしているならメソッドと読んだ方が良い。
irb(main):022> String.instance_methods == "abc".methods
=> true
irb(main):023> String.methods == "abc".methods
=> false
irb(main):024>

インスタンスにはインスタンス変数とクラスへの参照しかなくて、メソッドの実態は持ってなくて、メソッドの実態はクラスが持っているって言われて、なんとなくわかるんだけど、実際のコード見てないからまだ腑に落ちてない

RubyのClassクラスのインスタンスは、クラスそのもの。

クラスのメソッドは、Classクラスのインスタンスメソッド。
ややこしいけど、確かにそう

ArrayクラスはObjectクラスを継承しているので、「配列はオブジェクトである」と言える。
Objectクラスはあらゆるオブジェクトで便利に使えるメソッドを持っている(to_s等)
BasicObjectクラスは、Rubyのクラス階層のルートであり、基本的なメソッドをいくつか持ったクラスである。
irb(main):025> Array.superclass
=> Object
irb(main):026> Object.superclass
=> BasicObject
irb(main):027> BasicObject.superclass
=> nil
irb(main):028>

こうなっているのか
Rubyにおいては、クラスはモジュールである。
インスタンスの生成や継承を使うときはクラスを選択して、どこかでインクルードするときはモジュールを選択すれなど、目的に応じて使い分ければ、意図が明確になって良い。
irb(main):028> Class.superclass
=> Module
irb(main):029> Module.superclass
=> Object
irb(main):030>

さっき作ったHogeクラスもオブジェクトなのか。
irb(main):012* class Hoge
irb(main):013* def x
irb(main):014* @y = 1
irb(main):015* end
irb(main):016> end
irb(main):031> Hoge.superclass
=> Object
irb(main):032>

クラスが継承しているクラスと、そのクラスがどのクラスのインスタンスかは全く別の話だから、そこごっちゃにしない方が良いな。
クラスAから生成されたクラスB(オブジェクト)は、クラスAを継承していないくて、そこ関係ないってこと。

irb(main):038> Hoge.class
=> Class
irb(main):039>

オープンクラスは便利な側面もあるが、反対に、いろんなライブラリを使った開発をしていると、クラスが衝突して、意図しない動作をするプログラムになる恐れがある。
そのため、名前空間をクラスに導入した。

そのスコープにおける定数探索のパスを、このように取得できたのか。
=> Class
irb(main):039* module M
irb(main):040* class C
irb(main):041* module M2
irb(main):042* puts Module.nesting
irb(main):043* end
irb(main):044* end
irb(main):045> end
M::C::M2
M::C
M
=> nil
irb(main):046>

irb(main):060* module M
irb(main):061* T = "abdM"
irb(main):062* class C
irb(main):063* T = "abdC"
irb(main):064* module M2
irb(main):065* T = "abdM2"
irb(main):066* puts T
irb(main):067* end
irb(main):068* end
irb(main):069> end
abdM2
irb(main):001* module M
irb(main):002* T = "abdM"
irb(main):003* class C
irb(main):004* T = "abdC"
irb(main):005* module M2
irb(main):006* puts T
irb(main):007* end
irb(main):008* end
irb(main):009> end
abdC
=> nil
irb(main):010>
irb(main):001* module M
irb(main):002* T = "abdM"
irb(main):003* class C
irb(main):004* module M2
irb(main):005* puts T
irb(main):006* end
irb(main):007* end
irb(main):008> end
abdM
=> nil
irb(main):009>
irb(main):001> T = "hogehoge"
=> "hogehoge"
irb(main):002* module M
irb(main):003* class C
irb(main):004* module M2
irb(main):005* puts T
irb(main):006* end
irb(main):007* end
irb(main):008> end
hogehoge
=> nil

class
クラスのクラスはClassなのか
irb(main):009> Class.class
=> Class
irb(main):010>

メソッドを呼び出すと、Rubyは以下の2つのことを行う。
- メソッドを探す。これはメソッド探索と呼ばれる。
- メソッドを実行する。これにはselfと呼ばれるものが必要だ。
このプロセス(メソッドを探して実行する)は、すべてのオブジェクト指向言語で起きている。
なるほど

メソッド探索を一言でまとめると「Rubyがレシーバのクラスに入り、メソッドを見つけるまで継承チェーンを上ること」である。
なるほど

メソッド探索は、「右へ一歩、それから上へ」
多くの人がこのように図を描くので、この動きは「右へ一歩、それから上へ(one step to the right, then up)」ルールと呼ばれる。レシーバのクラスに向かって右へ一歩進み、メソッドが見つかるまで継承チェーンを上へ進むわけだ。
これわかりやすい。

MyClassはスーパークラスを指定していないので、デフォルトのスーパークラスであるObjectを継承していることになる。
これはわかる。何も継承してないと、スーパークラスがObjectだった。
継承チェーンにはmoduleも含まれる。
ancestorsメソッドを使えば、継承チェーンを見れる。
irb(main):015> 1.class.ancestors
=> [Integer, Numeric, Comparable, Object, PP::ObjectMixin, Kernel, BasicObject]
irb(main):016>
irb(main):018> Array.ancestors
=> [Array, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject]
irb(main):019>

モジュールをクラスに(あるいは別のモジュールに)インクルードすると、Rubyはモジュールを継承チェーンに挿入するのだ。それはインクルードするクラスの真上に入る。
これは知らんかった。
irb(main):019* module M1
irb(main):020* def my_method
irb(main):021* "M1#my_methos()"
irb(main):022* end
irb(main):023> end
=> :my_method
irb(main):024* class C
irb(main):025* include M1
irb(main):026> end
=> C
irb(main):027> class D < C; end
=> nil
irb(main):028> D.ancestors
=> [D, C, M1, Object, JSON::Ext::Generator::GeneratorMethods::Object, PP::ObjectMixin, Kernel, BasicObject]
irb(main):029>

モジュールに対しても、オープンクラスのような機能が提供されているのか
irb(main):030* module A
irb(main):031* def x
irb(main):032* "hogex"
irb(main):033* end
irb(main):034> end
=> :x
irb(main):038* class C
irb(main):039* include A
irb(main):040> end
irb(main):042> C.new.x
=> "hogex"
irb(main):043* module A
irb(main):044* def x
irb(main):045* "hogehogey"
irb(main):046* end
irb(main):047> end
=> :x
irb(main):048> C.new.x
=> "hogehogey"
irb(main):049>
irb(main):050>

Objectクラスは、Kernelモジュールをincludeしているので、すべての継承チェーンでKernelモジュールが挿入されている。

Rubyのコードは常にオブジェクトの内部で実行されるので、Kernelモジュールのメソッドはどこからでも呼び出せる。おかげでメソッドのprintが言語のキーワードのように見えるわけだ。
コードが常にオブジェクトの内部で実行されるのは知らんかったし、実感できない。
irb(main):050> Kernel.private_instance_methods.grep /^pr/
=> [:printf, :print, :proc]
irb(main):051> print "hoge"
hoge=> nil
irb(main):052>

Rubyのコードはオブジェクト(カレントオブジェクト)の内部で実行される。カレントオブジェクトはselfとも呼ばれる。selfキーワードでアクセスできるからだ。
カレントオブジェクトって言い方初めてきいた。
selfでアクセスできるのは知ってる。

selfの役割を担えるオブジェクトは同時に複数は存在しない。
これも実感できないから、そうなんだって感じだな。

Rubyのプログラムを開始すると、Rubyのインタプリタが作ったmainと呼ばれるオブジェクトの内部にいることになる。このオブジェクトはトップレベルコンテキストと呼ばれることもある。コールスタックがトップレベルにある時のオブジェクトだからだ。ここで言うトップレベルとは、メソッドを呼び出していない時、あるいは呼び出したメソッドがすべて戻ってきた時の状態だ
プログラム実行時は、mainオブジェクトの内部にいることになるのか(だからselfでmainを指している)。面白い。
irb
irb(main):001> self
=> main
irb(main):002> self.class
=> Object
irb(main):003> self.class.ancestors
=> [Object, PP::ObjectMixin, Kernel, BasicObject]
irb(main):004>

モジュールをincludeする順番によって、メソッド呼び出しが意図した挙動にならない時がありそうだから、そこは気をつけた方がよさそう
irb(main):005> module A; end
=> nil
irb(main):006> module B; end
=> nil
irb(main):007* class C
irb(main):008* include A
irb(main):009* include B
irb(main):010> end
=> C
irb(main):011> C.ancestors
=> [C, B, A, Object, PP::ObjectMixin, Kernel, BasicObject]
irb(main):012>
include Bが先に継承チェーンで呼ばれる。
リネームするか、includeの順番変えるかだな。

メソッドを呼び出すということは、オブジェクトにメッセージを送っているということなんだ
わかるようでわからない。

Rubyの動的ディスパッチとは、コード実行時に動的に呼び出すメソッドを決めて実行すること。
Rubyだとsendを使って実現できる。

> Pryのオブジェクトは、インタプリタの設定を自身のアトリビュートとして保持している。
これは知らなかった。

メソッドは動的に呼び出すだけではなくて、動的に定義することもできる。
Module#define_methodを使えば、メソッドをその場で定義できる。
メソッド名とブロックを渡す必要があり、ブロックがメソッドの本体となる。
実行時にメソッドを定義するこの方法を、動的メソッドと呼ぶ
irb(main):012* class MyClass
irb(main):013* define_method :my_method do |my_args|
irb(main):014* my_args * 3
irb(main):015* end
irb(main):016> end
=> :my_method
irb(main):017> obj = MyClass.new
=> #<MyClass:0x0000000125ddd828>
irb(main):018> obj.my_method(2)
=> 6
irb(main):019> obj.my_method("hg")
=> "hghghg"
irb(main):020>

シンボルは「何かの名前」に使われることがほとんどである。特にメソッド名などのメタプログラミングに関係する名前だ。シンボルが名前に適しているのは、文字列にある文字と違ってイミュータブル(変更不能)だからである。メソッド名が途中で変更されることはないはずだ。したがって、メソッド名を参照するときにはシンボルを使うことになる。
なるほど

メソッド探索でメソッドが見つからなかったら、レシーバのmethod_missingメソッドを呼び出す。
method_missingメソッドはBasicObjctにprivateインスタンスメソッドとして定義されている。
irb(main):042> BasicObject.private_instance_methods.grep /^method/
=> [:method_missing]
irb(main):043> BasicObject.new
=> #<BasicObject:0x0000000125db3cd0>
irb(main):044> BasicObject.new.method_missing
(irb):44:in `<main>': private method `method_missing' called for an instance of BasicObject (NoMethodError)
irb(main):067* class HG
irb(main):068* def me_m
irb(main):069* method_missing(:joffjo)
irb(main):070* end
irb(main):071> end
=> :me_m
irb(main):072> HG.new.me_m
(irb):69:in `me_m': undefined method `joffjo' for an instance of HG (NoMethodError)
method_missing(:joffjo)
^^^^^^^^^^^^^^
from (irb):72:in `<main>'
from <internal:kernel>:187:in `loop'
from /Users/yuuki_haga/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.1/exe/irb:9:in `<top (required)>'
from /Users/yuuki_haga/.rbenv/versions/3.3.1/bin/irb:25:in `load'
from /Users/yuuki_haga/.rbenv/versions/3.3.1/bin/irb:25:in `<main>'
irb(main):073> 1.length
(irb):73:in `<main>': undefined method `length' for an instance of Integer (NoMethodError)
1.length
^^^^^^^
from <internal:kernel>:187:in `loop'
from /Users/yuuki_haga/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.1/exe/irb:9:in `<top (required)>'
from /Users/yuuki_haga/.rbenv/versions/3.3.1/bin/irb:25:in `load'
from /Users/yuuki_haga/.rbenv/versions/3.3.1/bin/irb:25:in `<main>'
確かにできた

send使えば簡単に呼び出せた
irb(main):081> HG.new.send(:method_missing, :fuga)
(irb):81:in `<main>': undefined method `fuga' for an instance of HG (NoMethodError)
HG.new.send(:method_missing, :fuga)
^^^^^
from <internal:kernel>:187:in `loop'
from /Users/yuuki_haga/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.1/exe/irb:9:in `<top (required)>'
from /Users/yuuki_haga/.rbenv/versions/3.3.1/bin/irb:25:in `load'
from /Users/yuuki_haga/.rbenv/versions/3.3.1/bin/irb:25:in `<main>'
irb(main):082>

これが要はNo Method Errorで起きていること。解像度上がったな

ゴーストメソッドとは、レシーバ側には対応するメソッドが見た当たらないが、呼び出した際に通常の呼び出しのように呼べるメソッドのこと。
method_missingをオーバーライドして、missingの時の振る舞いを書いておけば、ゴーストメソッドを実現できる。
ライブラリ作る時で、同じようなメソッドをたくさん定義しないといけない場面で、使うとよさそうではあるが、仕事で使うのはやめといた方が良い。

継承先にメソッドが定義されていると、ゴーストメソッドに辿り着かないから、そこは注意。
クラスを生成したクラスが持っているインスタンスメソッドが、そのクラス(オブジェクト)のメソッドとなる。

sendとかdefine_methodを、ライブラリを使う人に使わせたくなくて、そうなると、メソッドを動的に増やすような方向性でライブラリ作るなら、ゴーストメソッドを採用するしかないのか

最小限のメソッドしかない状態のクラスをブランクスレートと呼ぶ
ブランクスレートクラスを継承すれば、ゴーストメソッドが呼ばれない問題も解決するのか

ゴーストメソッドはObject#methodsで登場しないのか。

irb(main):102* class HG
irb(main):103* def me_m
irb(main):104* method_missing(:joffjo)
irb(main):105* end
irb(main):106*
irb(main):107* def method_missing(name)
irb(main):108* "ghostmethods_#{name}"
irb(main):109* end
irb(main):110> end
irb(main):111> HG.new.method_missing("apple")
=> "ghostmethods_apple"
irb(main):112> HG.new.apple
=> "ghostmethods_apple"
irb(main):113> HG.new.display
#<HG:0x000000012656c5f8>=> nil
irb(main):114> HG.new.class.ancestors
=> [HG, Object, JSON::Ext::Generator::GeneratorMethods::Object, PP::ObjectMixin, Kernel, BasicObject]
irb(main):116> Kernel.instance_methods.grep /^method/
=> [:methods, :method]
irb(main):117> self
=> main
irb(main):118> self.class
=> Object
irb(main):119> self.class.ancestors
=> [Object, JSON::Ext::Generator::GeneratorMethods::Object, PP::ObjectMixin, Kernel, BasicObject]
irb(main):121> method(:display).owner
=> Kernel
irb(main):128> HG.new.methods.grep /^apple/
=> []
irb(main):129>
irb(main):130> HG.new.display
#<HG:0x00000001263e5ea0>=> nil
irb(main):131> HG.new.meron
=> "ghostmethods_meron"
irb(main):132>
irb(main):133> Kernel.instance_methods.grep /^method/
=> [:methods, :method]
irb(main):134> method(:display).owner
=> Kernel
irb(main):135>
irb(main):137> Object.instance_methods.grep /^method/
=> [:methods, :method]
irb(main):138> Object.instance_methods.grep /^display/
=> [:display]
irb(main):139>
irb(main):141> self.class.ancestors
=> [Object, JSON::Ext::Generator::GeneratorMethods::Object, PP::ObjectMixin, Kernel, BasicObject]
ゴーストメソッドを作ってみた
displayって名前を使うと、ゴーストメソッドが呼ばれなかった。
理由としては、Objectクラスにdisplayメソッドが定義されていて、メソッド探索時にそちらが見つかって呼ばれていたため。

module Kernel
全てのクラスから参照できるメソッドを定義しているモジュール。 Object クラスはこのモジュールをインクルードしています。
これ初めて知った
instance method Method#owner
このメソッドが定義されている class か module を返します。
これ意外と使える
instance method Object#method
オブジェクトのメソッド name をオブジェクト化した Method オブジェクトを返します。
ownerと組み合わせたら使える。

ブロックは任意の引数をyieldから受け取れる。
ブロックはメソッドを呼び出す時にだけ定義できる。
ブロックはメソッドに渡され、メソッドはyieldキーワードを使ってブロックをコールバックする。
Kernel#block_given?でブロックの有無を確認できる
jsのコールバック関数に似ているな。
irb(main):152* def abc(d)
irb(main):153* return yield if block_given?
irb(main):154* d
irb(main):155> end
=> :abc
irb(main):156> abc(1)
=> 1
irb(main):157> abc(2)
=> 2
irb(main):158> abc(1) { 5 }
=> 5
irb(main):161* def abc(d)
irb(main):162* return yield(d) if block_given?
irb(main):163* d
irb(main):164> end
=> :abc
irb(main):165> abc(2) { |x| x * 9 }
=> 18
irb(main):166> abc(2)
=> 2
irb(main):167> abc(2) { |x| "hoge_#{x}" }
=> "hoge_2"
irb(main):168>
メソッドの振る舞いをこちらが意図したものに変更できるのがメリットではあるか。

ブロックはメソッド実行時のコンテキストを覚えていて、メソッド内でブロックを実行する時は、メソッド実行時のコンテキストを見る。
irb(main):174* def abc(d)
irb(main):175* x = "hello"
irb(main):176* yield(d)
irb(main):177> end
=> :abc
irb(main):178> x = "HELLO"
=> "HELLO"
irb(main):179> abc(5) { |d| "#{x}_#{d}" }
=> "HELLO_5"
irb(main):180>

Kernel#local_variablesで、そのスコープで定義された変数が見れるのか。
irb(main):181> local_variables
=> [:x, :obj, :_]
irb(main):182>

ブロックを使うことで、変数をスコープを超えて参照したりできたりするのか