byebugのガイドをおおざっぱになぞってみる(4)

GUIDE.mdに沿って実際に動かしてみて、自分で自分に説明してみた記録。翻訳ではない。(というか英語的にどうかって言われると自信がない。)

Byebugのバージョンは9.0.6、Rubyのバージョンは2.4.0p0。

Introduction (続き)

Debugging Oddities: How debugging Ruby may be different from other languages

C言語など他のプログラミング言語でのデバッガを使ったことがある人にとっては、何だか変わっているなと思うところがあるかもしれない。そういう点のいくつかはデバッガそのものの違いというよりも、Rubyの動作の仕組みによるものだと言える。そのRubyのための使いやすいデバッガを作ろうとすると、やや変わったところが出てくる。そういう意味ではByebugを使うことでRubyをより理解できるということもあるかもしれない。

すでに見てきたように、メソッド定義のdefは実行される文であり、その場所でプログラムがストップした。コンパイル言語では、この種のことはコンパイル時に終えているのでこういうことは起きない。(あるいは、インタプリタ言語であるPerlでも、プログラムの解析時に行われるためストップしない。)

この節では、他言語での経験がある人たちを惑わせやすい点を見ていく。

Bouncing Around in Blocks (iterators)

PythonやRubyのようなコルーチンのある言語では、プログラムの実行がメソッドとの間で行き来する際、必ずしもその先頭から実行されるとは限らない。

以下のコードでは、32行目のメソッド呼び出しにより、プログラムの実行がメソッドnext_prime内の10行目に移る。そして11行目のyeild文により、プログラムの実行が呼び出し元の12行目に戻る。そして34行目の実行を終えると、再びnext_primeメソッドに実行が移るのだが、この際、プログラムはyield文に続く12行目から実行される。

#
# 素数を列挙する
#
class SievePrime
  def initialize
    @odd_primes = []
  end

  def next_prime
    candidate = 2
    yield candidate
    not_prime = false
    candidate += 1

    loop do
      @odd_primes.each do |p|
        not_prime = (0 == (candidate % p))
        break if not_prime
      end

      unless not_prime
        @odd_primes << candidate
        yield candidate
      end

      candidate += 2
    end
  end
end

sieve = SievePrime.new
sieve.next_prime do |prime|
  puts prime
  break if prime > 10
end
$ byebug primes.rb

[1, 10] in /private/tmp/primes.rb
    1: #
    2: # 素数を列挙する
    3: #
=>  4: class SievePrime
    5:   def initialize
    6:     @odd_primes = []
    7:   end
    8:
    9:   def next_prime
   10:     candidate = 2
(byebug) set linetrace
linetrace is on
(byebug) set basename
basename is on
(byebug) step 10
Tracing: primes.rb:5   def initialize
Tracing: primes.rb:9   def next_prime
Tracing: primes.rb:31 sieve = SievePrime.new
Tracing: primes.rb:6     @odd_primes = []
Tracing: primes.rb:32 sieve.next_prime do |prime|
Tracing: primes.rb:10     candidate = 2
Tracing: primes.rb:11     yield candidate
Tracing: primes.rb:33   puts prime
2
Tracing: primes.rb:34   break if prime > 10
Tracing: primes.rb:12     not_prime = false

[7, 16] in /private/tmp/primes.rb
    7:   end
    8:
    9:   def next_prime
   10:     candidate = 2
   11:     yield candidate
=> 12:     not_prime = false
   13:     candidate += 1
   14:
   15:     loop do
   16:       @odd_primes.each do |p|
(byebug)

(メモ: この節で言わんとしてことがいまいち分からない。内容を理解できていないかもしれない。)

No Parameter Values in a Call Stack

伝統的なデバッガでは、コールスタックにおいて受け渡されたパラメータの名前とその値を参照できる。

Rubyはとても動的な言語で、言語仕様の範囲内で効率的であろうとする。値を変数や式から取すのではなく、スタック上にプッシュする。新たなスコープが作られ、初期値が用意される。パラメータの受け渡しは参照渡しだ。メソッドの実行中、パラメータの値を変更できるし、変更される。異なったクラスの値に変更されることもある。

そんなわけでパスメータの名前が表示される。callstyleという設定で名前だけが表示されるか、名前と呼び出し時点のクラス名とが表示されるかを選択できる。shortを指定すると前者、特に指定しないかshort以外に設定すると後者になる。

(メモ: この節で言わんとしてことがいまいち分からない。内容を理解できていないかもしれない。)

Lines You Can Stop At

次の短いプログラムがある。この6行のうち、ストップの対象となるのは5行目と6行目だ。

'Yes it does' =~ /
(Yes) \s+
it  \s+
does
/ix
puts $1

Byebugのinfo命令を使ってinfo fileを実行すれば、プログラム中のどの場所でストップさせられるか調べることができる。