Rubyプログラミングの基礎知識

2014年7月3日(木)
黒田 努
実践Ruby on Rails 4 現場のプロから学ぶ本格Webプログラミング
顧客管理システムの構築を体験しながら、Railsアプリケーション開発のノウハウを習得!Amazon詳細ページへ
この記事は、書籍『実践Ruby on Rails 4 現場のプロから学ぶ本格Webプログラミング』の内容を、Think IT向けに特別にオンラインで公開しているものです。詳しくは記事末尾の書籍紹介欄をご覧ください。

ブロックとProcオブジェクト

この節はやや高度な内容を含みます。難しく感じられたらいったん読み飛ばし、時間をおいて復習してください。

ブロック

ブロックとは

ブロックは、Rubyの初学者を悩ませる概念の筆頭です。おそらく最初に出会うのが配列のeachメソッドに付随するブロックでしょう。たとえば次のような。

[ 2, 3, 5, 7, 11 ].each { |n| puts n ** 2 }

このコードを実行すれば、4、9、25、49、121という数が結果として表示されます。

これはもちろんブロックの由緒正しき利用法です。問題は、ブロックを伴うメソッドを自分で定義するときです。そのようなメソッドを自由自在に定義できるようになると、プログラミングの幅が格段に広がります。もちろんRailsによるアプリケーション開発でも役立ちます。

簡単な使用例を示しましょう。

chapter03/block01.rb

class Robot
  def job
    puts "Started."
    yield
    puts "Finished."
  end
end

robot = Robot.new
robot.job { puts "Hello World!" }

最終行の波括弧({})で囲まれた部分がブロックです。ブロックはメソッドにコードを渡すための仕組みです。「メソッドにコードを渡す」とは、どういう意味でしょうか。普通はメソッドに引数としてさまざまなオブジェクトを渡しますね。たとえばr.job(7)のように。この7というオブジェクトの代わりにputs "Hello World!"というコードを渡しているのです。

さて、4行目にyieldと書いてあります。ここでブロックが実行されます。3行目と5行目を見てください。ここに書かれているコードがブロックを実行する前と後に実行されます。

つまり、このプログラムを実行すると次のような結果となります。

$ ruby block01.rb
Started.
Hello World!
Finished.

なお、ブロックの開始と終了は波括弧({})の代わりにdoとendでも示せます。

robot.job do
  puts "Hello World!"
end

通常、私はブロックが1行に収まるときは波括弧({})、収まらないときはdoとendを使うことにしています。

ブロック変数

メソッドからブロックにオブジェクトを渡したいときはブロック変数を利用します。これも実例から学びましょう。

chapter03/block02.rb

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

  def job
    puts "Started."
    yield(@name)
    puts "Finished."
  end
end

robot = Robot.new("Alice")
robot.job { |name| puts "Hi, my name is #{name}!" }

ブロックの開始直後にあるパイプ文字(|)で囲まれたnameがブロック変数です。この例では、インスタンス変数@nameの値である"Alice"という文字列がブロック変数nameにセットされます。そして、そのnameの値を用いてputs "Hi, my name is #{name}!"というコードが実行されます。

このプログラムを実行すると次のような結果となります。

$ ruby block02.rb
Started.
Hi, my name is Alice!
Finished.

ブロックの活用法

次に、もう少し複雑なブロックの使用例をお見せしましょう。

chapter03/block03.rb

class Robot
  attr_reader :x, :y
  def initialize
    @x = 0
    @y = 0
  end

  def job
    x0, y0 = x, y
    yield(self)
  puts "(#{x0}, #{y0}) => (#{x}, #{y})"
  end

  def move(d1, d2)
    @x += d1
    @y += d2
  end
end

robot = Robot.new

robot.job do |r|
  r.move(1, 0)
  r.move(0, 1)
end

robot.job do |r|
  r.move(1, 0)
  r.move(1, 1)
  r.move(0, -3)
end

jobメソッドの定義に着目してください。

def job
  x0, y0 = x, y
  yield(self)
  puts "(#{x0}, #{y0}) => (#{x}, #{y})"
end

まずローカル変数x0とy0に属性xとyの値をセットしています。そして、ブロック変数としてself、つまり自分自身をセットしてブロックを実行します。この間に属性xとyの値は変化します。最後に属性xとyの値がどう変化したかを出力しています。

次に、このメソッドを呼び出す側のコードを読み解きましょう。Robotオブジェクトが生成された後、jobメソッドが2回呼ばれています。1回目の呼び出しはこうです。

robot.job do |r|
  r.move(1, 0)
  r.move(0, 1)
end

ブロックの内部ではブロック変数rのmoveメソッドが2回呼ばれています。このrの正体は何でしょうか。メソッドjobがyieldを通じて送ってくるselfすなわちRobotオブジェクトです。

上記コードに現れるローカル変数robotとブロック変数rは同じオブジェクトを指しています。しかし、メソッドの中でyield(self)が呼ばれたから同じになっただけで、常にそうなるわけではありません。混乱しやすいところです。注意してください。

ブロックの中でr.move(1, 0)というコードが実行されると、このr(Robotオブジェクト)の属性xに1が加算されます。同様に、次のr.move(0, 1)でrの属性yに1が加算されます。ですから、1回目のjobメソッドコールが終わるとターミナルには次のような結果が表示されていることになります。

$ ruby block03.rb
(0, 0) => (1, 1)

続いて2回目のjobメソッドコールに処理が移ります。

robot.job do |r|
  r.move(1, 0)
  r.move(1, 1)
  r.move(0, -3)
end

ローカル変数robotは前回と同じRobotオブジェクトを参照していて、そのRobotオブジェクトは自らの状態を記憶していますので、属性xとyの値は1と1です。そして、ブロック変数rのmoveメソッドが3回呼ばれます。属性xの値は2加算されて、属性yの値は2減らされています。

結局のところ、最終的なプログラムの実行結果は次のようになります。

$ ruby block03.rb
(0, 0) => (1, 1)
(1, 1) => (3, -1)

ところで、このプログラムの要点はどこにあるのでしょうか。単にRobotオブジェクトの属性をmoveメソッドで変更するだけならこんなに複雑なプログラムを書く必要はありません。私がしたかったのは、一連のmoveメソッドコールの前後で属性xとyがどう変化したかを画面に表示することです。ブロックを使うと、その目的をエレガントに達成できます。

通常のメソッド呼び出しはメソッド側で定義されたコードしか実行できません。しかし、ブロック付きでメソッドを呼び出す場合は、メソッド側で定義されたコードの中にメソッドを呼び出す側から別のコード(ブロック)を挿入することができるのです(図3-1)。

通常のメソッド呼び出しとブロック付きのメソッド呼び出し
図1 通常のメソッド呼び出しとブロック付きのメソッド呼び出し
株式会社オイアクス

東京大学教養学部卒。同大学院総合文化研究科博士課程満期退学。ギリシャ近現代史専攻。専門調査員として、在ギリシャ日本国大使館に3年間勤務。中学生の頃に出会ったコンピュータの誘惑に負け、IT業界に転身。株式会社ザッパラス技術部長、株式会社イオレ取締役を経て、技術コンサルティングとIT教育を事業の主軸とする株式会社オイアクスを設立。現在、同社代表取締役社長。また、2011年末にRuby on Rails によるウェブサービス開発専業の株式会社ルビキタスを知人と共同で設立し同社代表に就任(オイアクス社長と兼任)。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています