“binding.pry”って実際のところは何なのだろう

  • 186
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

この記事はPryのコミッターであるkyryloが書いたso-what-is-binding-pry-exactlyを訳したものです。日本語訳の公開を快く許可してくれたkyryloに感謝します。


Pryの使い方で一番最初に習うのは、“binding.pry”だと思います。この言葉をあちこちに埋め込むことで、幸せになれます。簡単にいうと、“binding.pry”を使うことで、日々の生活がだいぶ楽になります。あなたは“binding.pry”が何者なのか、不思議に思ったことはありませんか? 細かいことなので気にならなかったことだと思います。私がPryを使い始めたときは、これをおまじないの一種と考えました。“binding.pry”は不思議な魔法のようにみえたのです。魔法などでないことは、確かです。しかし、新しいことを学ぶとき、実際より不思議に思えてしまうことはよくあることです。この記事を読み終わる頃には、Pryの最も古く、最も面白い機能について、十分理解しているはずです。

信じられない力をもったフレーズについて

“binding.pry”はたいていの場所で使うことができます。Pryをコマンドライン経由でたちあげると、デフォルトのコンテキストは"main"になります。Pryはこの情報をプロンプトに(“[1] pry(main)>”)というかたちで表示します。プロンプトには現在のコンテキストが表示されます。

次のコードをみると、“binding.pry”は様々な場所に書き込むことができることが分かります。違いは、どのコンテキストにいるかです。

class Song
  # PryはSongのコンテキストから開始します。
  binding.pry # pry(Song)

  def play
    puts 'Ground control to Major Tom...'
  end
end
class Song
  def play
    # PryはSongのインスタンスのコンテキストから開始します。
    binding.pry # pry(#<Song>)
    puts 'Ground control to Major Tom...'
  end
end
class Song
  def play
    puts 'Ground control to Major Tom...'
  end
end

# コンテキストはmainです。
binding.pry # pry(main)

“binding.pry”を“binding”と“pry”に分ける

いよいよ秘密のベールをはがしていきましょう。Binding classについて知らない人も多いかと思います。多くの人はその名前すら聞いたことないと思います。Bindingに関しては、すべてのRubyistが知っていなければならない機能ではありません。しかし、Bindingあってこその“binding.pry”なのです。詳しく見ていきましょう。

“binding”

"binding"はメソッドです。Bindingクラスのインスタンスを新しく作成し、返します。

binding.class #=> Binding
binding #=> #<Binding:0xa9dcefc>
binding #=> #<Binding:0xa9ee490>

どこでも好きなところで、このメソッドを実行できます。というのも、"binding"メソッドはKernelに定義されており、Kernelに定義されたメソッドはすべてのオブジェクトで使用可能だからです。もっと大切なこととしては、Bindingクラスから直接インスタンスをつくることはできないということです。"binding"メソッドが唯一のインターフェイスです。

では"binding"とはなんなのでしょう? bindingとはbindingが作られたときに利用可能なすべてのものを切り取った、"スナップショット"といえます。利用可能なすべてのものとは、その時点の"self"の値、ローカル変数、メソッド、インスタンス変数などです。家具や雑貨がいっぱいある部屋を想像してみてください。いろいろな部屋があります。それぞれの部屋には様々な雑貨があり、窓が一つあります。Pryでは、そのような場所をコンテキストと呼んでいます。雑貨を部屋から取り出すために、pryを使って窓を開けて、家のなかにこっそり入ってみましょう。

Bindingクラスのおかげで、1回のメソッド呼び出しで、窓(window)に相当するものを実装することができます。そのようなメソッドを"Room#window"として実装し、テディベア(teddy bear)を部屋のなかにおいておきましょう(訳者補足:"Room#window"はRoomクラスに定義されたwindowというインスタンスメソッドの意味です)。

class Room
  def initialize
    @items = [:teddy_bear]
  end

  def window
    binding
  end
end

backpack = []
bedroom = Room.new

bedroom.window.eval('@items') #=> [:teddy_bear]
backpack << bedroom.window.eval('@items.pop')
backpack.inspect #=> [:teddy_bear]
bedroom.window.eval('@items') #=> []

あとで誰も泣かないように、マリア(Maria)にお願いしてテディベアをもとの部屋にもどしてもらいましょう。"binding"のおかげでマリア(Maria)のコンテキストにいながら、部屋(room)のコード実行することができます。

module Maria
  def self.take_back(item, from, to)
    eval("@items << #{ from.delete(item).inspect }", to)
  end
end

Maria.take_back(:teddy_bear, backpack, bedroom.window)
backpack.inspect #=> []
bedroom.window.eval('@items') #=> [:teddy_bear]

"pry"

次に“binding.pry”の"pry"の部分を見てみましょう。たいていのRubyオブジェクトで、"pry"メソッドを実行できます。というのも"pry"メソッドはObjectに定義されているからです。Rubyのクラスは必ずObjectを継承しています。"pry"メソッドが呼ばれると、呼び出したobjectのコンテキストでPryの新しいセッションが始まります。

[1] pry(main)> ['andrew', 'alexander', 'vladimir'].pry
[1] pry(#<Array>)> map &:capitalize
=> ["Andrew", "Alexander", "Vladimir"]
[2] pry(#<Array>)> exit
=> nil
[2] pry(main)> "do you hear me?".pry
[1] pry("do you hear me?")> upcase
=> "DO YOU HEAR ME?"
[2] pry("do you hear me?")> exit
=> nil
[3] pry(main)>

この記事の最初に紹介したコードと、いまの説明から、「"binding.pry"の代わりに、"pry"と書くことができるのでは?」と思うかもしれません。たしかにできます。しかし"binding.pry"と"pry"は必ずしも同じではないのです。この二つの書き方はそれぞれ違ったコンテキストでPryのセッションを開始します。

つぎのコードでは“binding.pry”を使用しています。Songインスタンスのコンテキストで、Pryのセッションが開始します。ローカル変数の"music"にアクセスできます。

class Song
  def play
    music = 'rock & roll'
    "I love #{ binding.pry }"
  end
end

Song.new.play
[1] pry(#<Song>)> music
=> "rock & roll"

さきほどのコードに少し手を加えて、"pry"を利用するようにしましょう。このときもSongインスタンスのコンテキストで、Pryのセッションが開始します。しかし先ほどとはちょっと違います。この場合、ローカル変数の“music”にはアクセスできません。“Song#play”のなかで"pry"を実行しているのに、想像していたのとは違う場所でPryのループが始まります。ローカル変数に触ることはできません。"Song#play"のbindingに対してPryが動いているわけではないからです。代わりにPryはSongインスタンスに対して直接動いています(Songインスタンスとは"self"経由でアクセスできるやつです)。

class Song
  def play
    music = 'rock & roll'
    "I love #{ pry }"
  end
end

song = Song.new #=> #<Song:0x9d89588>
$old_id = song.object_id #=> 82594500
song.play
[1] pry(#<Song>)> music
NameError: undefined local variable or method `music' for #<Song:0xbd2fe00>
from (pry):7:in `__pry__'
[2] pry(#<Song>)> whereami
Inside #<Song>.
[3] pry(#<Song>)> object_id == $old_id
=> true

"self"はObjectから"pry"メソッドを取得します。“self.pry”と“pry”は結局同じことです。

"pry"メソッドを実行できないケースが1つあります。PryはBasicObjectのインスタンスについてはサポートしていません。というのもBasicObjectはObjectのsuperclassであり、継承のチェーンの上位にいるからです。つまりBasicObjectのインスタンスは"pry"メソッドをもたないことになります。またBasicObjectは"binding"メソッドをもたないため、"Object#pry"を“BasicObject#pry”に移動することもできません。

basic = BasicObject.new #=> #<BasicObject:0x512c30c>
basic.pry #=> NoMethodError: undefined method `pry' for #<BasicObject:0xa258618>

Rubyの設計では、"binding"メソッドはKernelモジュールに定義されており、KernelをincludeしているのはObjectだけです。BasicObjectがbindingをサポートすることを望んでいる人もいますが
しばらくは実現しそうにありません。Pry界隈でもサポートするよう働きかけたことがありますが、うまくいってません。

PryはBasicObjectのインスタンスについてはサポートしていませんが、BasicObjectクラスそのものについてはサポートしています。Classインスタンスはbindingsをもっているからです。

[1] pry(main)> BasicObject.pry
[1] pry(BasicObject)> __id__
=> 81839890

"pry"メソッドの面白い使い方として、引数を渡すという使い方があります。じつはpryself.prypry(self)も同じことなのです。"pry"メソッドは"self"以外にもすべてのobject
を引数にとれます。"pry"は1メソッドにすぎませんが、強力なメソッドなのです。

次の例をみると、"pry"メソッドの動きが(pryの)"cd"コマンドとは異なるのが分かります。"cd"コマンドのように、bindingsのチェーン(binding stackといいます)が切り替わっています。"pry"メソッドは新しいセッションを作成し、新しいセッションは独自のbinding stackをもちます(プロンプトの“[1]”というところをみると、新しく1から始まっていることが分かります)。

[1] pry(main)> pry 1337
[1] pry(1337)> pry ''
[1] pry("")> pry :awesome!
[1] pry(:awesome!)>
[1] pry(:awesome!)> nesting
Nesting status:
--
0. :awesome! (Pry top level)
[2] pry(:awesome!)> exit
=> nil
[1] pry("")> nesting
Nesting status:
--
0. "" (Pry top level)

"pry"メソッドは第二引数にハッシュオプションを渡すことができます。ほとんどの場合、オプションを使うことはないでしょう。しかしあなたは特別なユーザーですので、一般的なpryユーザーよりもpryについて詳しくなっても損ではないでしょう。全オプションは“pry_instance.rb”で見ることができます(Pry v0.9.12.2をベースに話をしています)。全部を紹介すると多いので、ここでは面白いオプション2つを紹介します。“:output”と“:extra_sticky_locals”です。

ここでは2つのことを行います。1つめは、ネストしたセッション内で実行したすべての結果を“output_history”というローカル変数にリダイレクトします。そして後ほど、その結果を表示します。2つめは、stickyなローカル変数を導入します。stickyな変数は1つのセッションで共有され、どのコンテキストからもアクセスできます(訳者補足:原文ではvariables are shared across all Pry sessionsですが、extra_sticky_localsは同一セッション内の異なるコンテキストで共有されるため訳を変えました)。なお"pry"メソッドを実行する度に新しいセッションが作られるので、ハッシュオプションはオプションを渡したセッション内でのみ有効です。

[1] pry(main)> output_history = StringIO.new
=> #<StringIO:0xab8978c>
[2] pry(main)> :universe.pry :output => output_history, :extra_sticky_locals => { :time => Time.now }
[1] pry(:universe)> whereami
[2] pry(:universe)> ls
[3] pry(:universe)> time
[4] pry(:universe)> Help me out!
[5] pry(:universe)> exit
=> nil
[3] pry(main)> puts output_history.string
Inside :universe.
Comparable#methods: <  <=  >  >=  between?
Symbol#methods: 
  <=>  =~       capitalize  empty?    inspect  match               size   swapcase  to_sym
  ==   []       casecmp     encoding  intern   next                slice  to_proc   upcase
  ===  __pry__  downcase    id2name   length   pretty_print_cycle  succ   to_s    
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  _pry_  time
=> 2013-05-25 14:22:34 +0300
NoMethodError: undefined method `out!' for :universe:Symbol
from (pry):3:in `__pry__'
=> nil
[4] pry(main)> time
NameError: undefined local variable or method `time' for main:Object
from (pry):8:in `__pry__'

“binding”と“pry”を結びつける

“binding”と“pry”に関する理解が深まったところで、メインの問題にもどりましょう。Pry はどのようにしてREPL(read-eval-print-loop)の開始地点を決めるのでしょうか。なぜ“binding.pry”という書き方でいいのでしょうか。簡単にいうと、あるオブジェクトに対して"pry"メソッドを実行すると、Pryはそのオブジェクトのbindingを獲得し、そのコンテキストからREPLを開始します。bindingを取り出す仕組みは単純です。“Pry.binding_for”を使います。

b = Pry.binding_for(:universe) #=> #<Binding:0xb2e7ad8>
b.eval('self') #=> :universe

このメソッドはすべてのobjectを受け付けます。Bindingのインスタンスやトップレベルのbindingを渡すと、引数自身を返します。それ以外のときの挙動はすこし違います。“:universe”に対してはbindingというメソッドを呼び出し、bindingを取得します。":universe"はbindingをどこで知ったのでしょう。実はbindingはObjectに定義されているのです。

bindingを取得するために、内部的には別のメソッドが使われています。"pry"メソッドを呼ぶと、対象となるobjectにpryというメソッドが定義されます。

[1] pry(main)> :universe.__pry__
NoMethodError: undefined method `__pry__' for :universe:Symbol
from (pry):1:in `__pry__'
[2] pry(main)> :universe.pry
[1] pry(:universe)> :universe.__pry__
=> #<Binding:0xadf1a74>
[2] pry(:universe)> 
=> nil
[3] pry(main)> :universe.__pry__
=> #<Binding:0x91a27c4>
[4] pry(main)> :universe.__pry__.eval('self')
=> :universe

ものすごく難しいことをしているわけではありません。単純にインスタンスのbindingを取得しているだけです("pry"は、ちょっと前にやった“Room#window”と同じようなものです)。このメソッドは内部的に利用するものですが、このメソッドを使うことで面白いことができます。一度"pry"が定義されれば、その後はpryをしなくてもオブジェクトのなかを見ることができます。このメソッドがオブジェクト内部へのゲートウェイになるのです。

[1] pry(main)> 1337.pry
[1] pry(1337)> @leet_number = :so_leet
=> :so_leet
[2] pry(1337)> exit
=> nil
[2] pry(main)> 1337.__pry__.eval('@leet_number')
=> :so_leet

すでに話したとおり、Pryは“binding”というメソッドをすべてのオブジェクトに定義しています。そのため“pry”メソッドを呼ぶ必要はないのです。

[1] pry(main)> :universe.__binding__.eval 'upcase'
=> :UNIVERSE

なぜ似たようなメソッドが2つあるのでしょうか? これらは全く同じというわけではないからです。“pry”は“binding”よりも内部にあります。“binding”が“pry”を利用する関係です。そのため、“binding”のほうが多くの機能をもっています。ClassやModuleには"pry"メソッドは定義されていませが、"binding"メソッドは定義されています。PryはClassのコンテキストからでもセッションを始めることができましたね。これはひとえに"binding"メソッドのおかげです。

A = Class.new
A.__pry__ #=> NoMethodError
A.__binding__ #=> #<Binding:0xb0e8570>

A.pry
exit # Exits from the nested session.
A.__pry__ #=> NoMethodError, still undefined.

つまり"pry"メソッドはインスタンス用ということです。それ以外の場合、"binding"が使えます。どちらを使った場合でも、selfのコンテキストにいるものとして評価されます。ModuleはClass同様の動きをします。

[1] pry(main)> M = Module.new
=> M
[2] pry(main)> M.__binding__.eval('def magnifico; :splendid end')
=> nil
[3] pry(main)> include M
=> Object
[4] pry(main)> magnifico
=> :splendid

REPLとコンテキストをくっつけてみましょう。

[1] pry(main)> loop do
             |   print '>> '
             |   puts "=> #{ TOPLEVEL_BINDING.eval(gets) }"
             | end
>> def hello; :hi end
=>
>> hello
=> hi

いろいろなものをbindingsで味付けして、有名人になりましょう。

Pryの歴史

ざっくり言うと、初期のPryでもほとんど同じことができます。APIは異なっていて、“binding.pry”の代わりに“Pry.into(object)”を使います。いまのPryは仕組みがしっかりとし、使いやすくなっています。3年前のPryを一度見てみてください。わずか125行のコードでできています。面白いことに、READMEにはこう書いてあります。"PryはIRBの代わりにはなりません"。またIRBの代わりになるものとして、riplというgemをお勧めしていました。

まとめ

“binding.pry”は強力です。“binding”はそれだけでも便利ですが、Pryと組み合わせることで、信じられないくらいの可能性がみえてきます。Pryはいろいろなコンテキストからはじまります。デフォルトのコンテキストは"main"です(IRBと同じです)。bindingsを利用すればどこからでもセッションを開始できます。bindingの機能を拡張し、自由に中をみれるようになります。いままでで一番便利なRuby向けのデバッグツールを手に入れることができます。

宿題

Pryとbindingsについてもっと詳しくなりたい人のために、3つの宿題を用意しました。最初の問題はシンプルで、この記事のすべての読者にお勧めできます。次の問題もシンプルですが、ちょっと考える必要があります。最後の問題は難しいです。熱狂的なPryユーザー向けです。Pryのソースコードをあちこち見る必要があります。

問1

なぜローカル変数のmsgにアクセスできないのでしょうか? また、どうやったらアクセスできるのでしょうか?

class Duck
  def initialize(name)
    @name = name
  end

  def quack
    msg = 'quack, quack, quack!'
    :binding.pry
    puts "I'm #@name", "I #{ msg }."
  end
end

duck = Duck.new('Donald')
duck.quack

問2

Pryのコンソールで次のコードを実行してください。

count = 10

def exercise
  binding.pry
end

exercise

Pryのコマンド(たとえば"cd"など)を使わずに、ローカル変数のcountの値を入手してください。

問3

トップレベルのbindingを新しいものに変えてください。

[1] pry(main)> count = 1000
=> 1000
[2] pry(main)> # Some lines after...
[3] pry(main)>
[4] pry(main)> count
NameError: undefined local variable or method `count' for main:Object

ちなみに、“count = nil”とするのは正しくありません。そのようにやっても現在のbindingが新しいものになるわけではありません。GCやその他の関係ないものではなく、bindingに注目してください。この問に答えるには、単純に現在のbindingオブジェクトを新しいものに変えるだけでいいのです。

参考文献

私よりも上手に記事を書く人たちがいます。ここにあげた記事はPryに関するものではないですが、読むことで“binding.pry”への理解が深まります。ぜひ読んでみてください。

Three implicit contexts in Ruby — Yuki Sonoda
Variable Bindings in Ruby — Jim Weirich

知ってましたか?

IRBもbindingsの基本的な部分をサポートしているって、知っていますか? Pryのほうが便利だと思いますけど...

謝辞

友人でありPryの作者である John Mair、この記事の仕上げを手伝ってくれてありがとう。Duncan Beevers、貴重な意見をありがとう。