要は以下の記事の繰り返しなのだが。
Kaigi on Rails _2022_ new というイベントの LT で、メソッド定義を探ろうという話があった。
Rails のソースをシュッと眺めに行くという、非常に尊い良い発表でした。
Object のことは Object に聞け、は Ruby の非常に面白いところなので、Method を取り出して source_location
を尋ねるのは一度体験して感動して欲しいんだけど、実務だとタイプ数の少ないやり方も知っておくと更に便利に使えるのでご紹介。
irb の show_source も武器に加えてあげたい #kaigionrails
— Takafumi ONAKA (@onk) October 9, 2022
Pry の $
irb よりも色々できて便利な REPL、というのでよく使われている gem だと思う。その後 irb が追いついて追い越していったので多分使いどころは減ってきているが、既存のコードベースには入っていることも多いだろう。
pry には show-source
(alias の $
をよく使う) という機能があって、メソッドの定義に非常に簡単にアクセスできる。
https://github.com/pry/pry/blob/v0.14.1/lib/pry/commands/show_source.rb
使い方:
$ bundle exec pry [1] pry(main)> require "set" => true [2] pry(main)> $ Set#merge From: /opt/rubies/3.1.2/lib/ruby/3.1.0/set.rb:603: Owner: Set Visibility: public Signature: merge(enum) Number of lines: 9 def merge(enum) if enum.instance_of?(self.class) @hash.update(enum.instance_variable_get(:@hash)) else do_with_enum(enum) { |o| add(o) } end self end
メソッド表示時に Syntax Highlight も入っていて、非常に見やすい。
pry には pry-doc という plugin もあって、これを使うと C 言語で実装されているメソッドも読みに行けるのが好きです。
[1] pry(main)> $ puts From: io.c (C Method): Owner: Kernel Visibility: private Signature: puts(*arg1) Number of lines: 9 static VALUE rb_f_puts(int argc, VALUE *argv, VALUE recv) { VALUE r_stdout = rb_ractor_stdout(); if (recv == r_stdout) { return rb_io_puts(argc, argv, recv); } return forward(r_stdout, rb_intern("puts"), argc, argv); }
最近 こちらのエントリ でも紹介されていた。(あれオレ)
IRB の show_source
irb にも show_source
コマンドがある。というか pry の同機能が欲しくて id:k0kubun さんが入れた。
https://github.com/ruby/irb/blob/v1.4.2/lib/irb/cmd/show_source.rb
irb の show_source
も IRB::Color
という Syntax Highlight の仕組みが使われていて、読みやすくなっている。
なお、irb の制限として「Rubyとしてvalidな入力しかできない」というのがあり、pry と同じ書き心地ではない ($
の alias は無いし、引数も文字列として渡さなければいけない) が、それでも irb に show_source
があるのは最高です。というか、pry の show-source
を多用しまくっていたので、これが無いと僕は pry を卒業できなかった。
そして、エントリにもある通り .irbrc
に細工をすると $
を再現できる。
# .irbrc IRB::Context.prepend(Module.new{ def evaluate(line, *, **) case line when /\A\$ / line.replace("show_source #{line.sub(/\A\$ /, '').strip.dump}\n") end super end })
軽く説明すると
IRB::Context.prepend
- 入力を eval する部分に介入したいので
IRB::Context#evaluate
をprepend
で上書きする
- 入力を eval する部分に介入したいので
Module.new{}
prepend
&&super
するために無名モジュールを作っている
when /\A\$ /
- 入力が
$
で始まっていたら
- 入力が
line.replace("show_source #{line.sub(/\A\$ /, '').strip.dump}\n")
show_source "..."
に書き換えて
super
evaluate
を実行する
はい。
これでタイプ数少なく実装を読みに行けるようになった。show_source
が期待と違った、という人もこれなら満足するんじゃないでしょうか。
$ irb irb(main):001:0> $ Set#merge From: /opt/rubies/3.1.2/lib/ruby/3.1.0/set.rb:605 def merge(enum) if enum.instance_of?(self.class) @hash.update(enum.instance_variable_get(:@hash)) else do_with_enum(enum) { |o| add(o) } end self end => nil
irb 上の入力を加工して eval する手段もこれで分かったので、自前の面白機能を育てていくと良いと思う。
ここがもう一声
pry の show-source
は --super
がメチャクチャ便利なので、欲しい。
どんな機能かというと、show-source でメソッドを読んでいるときに super
が出てきたら --super
を付けると親クラスのメソッドを表示できるヤツです。
こういう C < B < A というクラスがあるとして、
class A def foo puts "A" end end class B < A def foo puts "B" super end end class C < B def foo puts "C" super end end
--super
を付けて呼ぶとこうなる。
[1] pry(main)> $ C.new.foo From: (pry):13: Owner: C Visibility: public Signature: foo() Number of lines: 4 def foo puts "C" super end
[2] pry(main)> $ C.new.foo --super From: (pry):7: Owner: B Visibility: public Signature: foo() Number of lines: 4 def foo puts "B" super end
複数回 --super
を渡すと super_level が上がる。
[3] pry(main)> $ C.new.foo --super --super From: (pry):2: Owner: A Visibility: public Signature: foo() Number of lines: 3 def foo puts "A" end
というのが欲しいんだけど、引数に渡したオブジェクトの ancestors を --super
の数だけ遡ればできそうではあるな。 .irbrc
でやってみるかー。