Archive for January, 2011

January 27, 2011

Turning IRB on its head with Pry

IRB is a great tool and is perfect for experimenting with small code samples and testing out new ideas. It has some difficulty however when the code samples become a bit larger or you’d like to start an IRB session half-way through a method, for example.

Pry, in some sense, is IRB turned on its head. Instead of having to bring your code to a REPL session (as with IRB) you instead bring a REPL session to your code, see the following:

# test.rb
require 'pry'

class A
  def hello() puts "hello world!" end
end

a = A.new

# start a REPL session
binding.pry

# program resumes here (after pry session)
puts "program resumes here."

We then run ruby test.rb from the command line and the following REPL session begins:

Beginning Pry session for main
pry(main)> a.hello
hello world!
=> nil
pry(main)> def a.goodbye
pry(main)*   puts "goodbye cruel world!"
pry(main)* end
=> nil
pry(main)> a.goodbye
goodbye cruel world!
=> nil
pry(main)> exit
Ending Pry session for main
program resumes here.

There are a few things to note from above:

  • The first is that local variables are available to a Pry session (locals are not available to an irb session when using `irb -r`).
  • The second is that when you end a Pry session it returns to the running program; this makes Pry particularly useful for debugging.

Debugging

Pry sits somewhere between using `puts` statements to debug and using a bona fide debugger. It effectively opens up an IRB-like session at the place it’s called and makes all the program state at that point available.

In the example below, the code (sans the `binding.pry`) works as required in Ruby 1.8 but in 1.9 it returns the wrong result – notably check_array([]) displays "ary has content"; whereas we want it to output "ary is empty!" We start a Pry session right after the line b = *ary to try to determine the problem in Ruby 1.9:

def check_array(ary)
  b = *ary

  # invoking Pry here for debugging
  binding.pry

  if !b
    puts "ary is empty!"
  else
    puts "ary has content"
  end
end

check_array([])

When running the above code the following Pry session starts up:

Beginning Pry session for main
pry(main)> show-method
def check_array(ary)
  b = *ary

  binding.pry

  if !b
    puts "ary is empty!"
  else
    puts "ary has content"
  end
end
=> nil
pry(main)> b
=> []
pry(main)> test = *[]
=> []
pry(main)> b = !ary.empty?
=> false
pry(main)> exit
Ending Pry session for main
ary is empty!

From above the first thing we do is display the code for the method we’re debugging – Pry’s `show-method` command does this.

We next experiment and find that the behaviour of *[] has changed in 1.9 (in 1.8 it returned nil) and this must be the cause of our problems. We then come up with a new test b = !b.empty? and let the program continue (by typing `exit`) with this new value of b. It now outputs the expected result.

Note that any changes to state we make in a Pry session persist during the lifetime of the program. This fact also makes Pry useful for interactively modifying runtime state.

Interactively modifying runtime state

It may be convenient to open a Pry session in the middle of a running program. The example below is of a game where we are using Pry to increase the lunar lander’s fuel; we also do a bit of exploration of the runtime state to show off some features of Pry.

Beginning Pry session for #
pry(#)> ls
[:_, :_pry_,:@state, :@frame_counter, :@playgame]
=> nil
pry(#)> cd @playgame
Beginning Pry session for #
pry(#):1> ls
[:_, :_pry_, :@map, :@font,  :@wind, :@objects, :@lander]
=> nil
pry(#):1> cd @lander
Beginning Pry session for #
pry(#):2> @fuel = 300000
=> 300000
pry(#):2> nesting
Nesting status:
--
0. # (Pry top level)
1. #
2. #
=> nil
pry(#):2> cd ..
Ending Pry session for #
=> nil
pry(#):1> ls --methods
[:__binding_impl__, :draw, :initialize, :lander, :level, :map]
=> nil
pry(#):1> cd map
Beginning Pry session for #
pry(#):2> ls
[:_, :_pry_, :@nebula, :@nebula_theta, :@moonscape]
=> nil
pry(#):2> exit-all
Ending Pry session for #
Ending Pry session for #
Ending Pry session for #

From above, Pry makes it easy to navigate runtime state. You can pop in and out of objects, nesting sessions as deeply as you like. The `cd` and `ls` commands are provided to make this navigation seem familiar and natural.

Customizability

Pry is also easily customizable – you can trivially set the input for a Pry session to objects other than `Readline` and `$stdin`; and likewise set the output object to something other than `$stdout`.

Many other features of Pry can also be customized making Pry a perfect choice for implementing custom shells. See Customizing Pry for more information.

An IRB Alternative

Pry can be invoked from the command line using the `pry` executable. It can then be used as an alternative to IRB. Many of the IRB command line options are supported. Type `pry –help` at the command line for more information.

Conclusion

Pry is an IRB-alike that can be invoked at any time and anywhere in the program and on any receiver; it can also receive input from anywhere and send output to anywhere. The examples shown here illustrate just some of the functionality of Pry, read the Wiki for the full story.

Tags: , ,