要は以下の記事の繰り返しなのだが。
k0kubun.hatenablog.com
Kaigi on Rails _2022_ new というイベントの LT で、メソッド定義を探ろうという話があった。
speakerdeck.com
Rails のソースをシュッと眺めに行くという、非常に尊い良い発表でした。
Object のことは Object に聞け、は Ruby の非常に面白いところなので、Method を取り出して source_location
を尋ねるのは一度体験して感動して欲しいんだけど、実務だとタイプ数の少ないやり方も知っておくと更に便利に使えるのでご紹介。
Pry の $
https://github.com/pry/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
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
に細工をすると $
を再現できる。
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
で上書きする
Module.new{}
prepend
&& super
するために無名モジュールを作っている
when /\A\$ /
line.replace("show_source #{line.sub(/\A\$ /, '').strip.dump}\n")
super
はい。
これでタイプ数少なく実装を読みに行けるようになった。show_source
が期待と違った、という人もこれなら満足するんじゃないでしょうか。
$ irb
irb(main):001:0> $ Set
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
でやってみるかー。