Ruby初級者向けレッスン 44回 ― ブロック ―
第56回 Ruby/Rails勉強会@関西での初級者向けレッスンのスライドを公開します。*1
繰り返し
a = [0, 1, 2] a.each do |i| puts i end a.each{|i| puts i} # >> 0 # >> 1 # >> 2
- ブロックの代表的な使い方は繰り返し処理。
- do と end で囲まれたもの、{ と } で囲まれたものがブロック。
- 上のコードと下のコードは同じ処理をしている。
- Array のように、たくさんのオブジェクトを持っていると、全てのオブジェクトに繰り返し同じ処理をしたいことがよくある。
- | と | で囲まれた変数 i が Array の各要素を順に指す。
便利な例
a = [0, 1, 2, 3] # => [0, 1, 2, 3] a.map{|i| i * i} # => [0, 1, 4, 9] a.select{|i| i.even?} # => [0, 2] a.inject{|s, i| s + i} # => 6 a.find{|i| i.odd?} # => 1 a.all?{|i| i.even?} # => false a.any?{|i| i.even?} # => true
- 初級者には、このスライドがおすすめ。
- 特に inject メソッド
ブロックを渡す
- メソッドには、ブロックをひとつ渡せる。
- ブロックをどう使うかは、メソッド次第。
- 繰り返し
- ハリウッドの原理
open('hello.txt') # => #<File:hello.txt> open('hello.txt'){|f| f.read} # => "こんにちは\n"
- ブロックの役割りは、繰り返しだけではない。
- ブロックの有無で動作を変えるメソッドがある。
ハリウッドの原理
# open('hello.txt'){|f| f.read} begin f = open('hello.txt') f.read ensure f.close unless f.nil? end
- 処理のスケルトンをメソッドで用意しておき、処理の一部をブロックで切り替える。
- ブロック付きの 1行で書いた open メソッドは、ブロックなしで書くと、だいたいこんな感じ。
- close の部分は f.close unless f.closed? かも。
値を受け取る
- ブロックは、値を受け取れる。(多重代入)
- 何を幾つ受け取れるかは、メソッド次第。
- 参考
- 第54回 Ruby/Rails勉強会@関西
- Ruby初級者向けレッスン 42回
- 繰り返しと多重代入
値を受け取る (2)
- 受け取るか受け取らないかは、ブロック次第。
2.times{puts 'こんにちは'} # >> こんにちは # >> こんにちは 2.times{|i| puts i} # >> 0 # >> 1
- 上の例は値を受け取っていない。
- しかし times メソッドは値を渡してくれる。(下の例)
- ブロックは必ずしも値を受け取る必要はない。
Hash の例
people = {matz: 47, dhh: 32} # => {:matz=>47, :dhh=>32} people.each{|person| p person} # >> [:matz, 47] # >> [:dhh, 32]
- key と value のペアを受け取れる。
- ふたつの値が、ひとつの Array オブジェクトに。
Hash の例 (2)
people = {matz: 47, dhh: 32} people.each do |name, age| p "#{name}(#{age})" end # >> "matz(47)" # >> "dhh(32)"
- ふたつの値を、それぞれ別の変数で受け取れる。
- | と | の間に , で区切って変数を列挙する。
each_cons の例
midosuji = ["梅田", "淀屋橋", "本町", "心斎橋", "なんば"] midosuji.each_cons(2){|path| p path} # >> ["梅田", "淀屋橋"] # >> ["淀屋橋", "本町"] # >> ["本町", "心斎橋"] # >> ["心斎橋", "なんば"]
- ふたつの値を、ひとつの変数で受け取ると Array オブジェクトになる。
each_cons の例 (2)
midosuji.each_cons(2) do |from, to| p "#{from} - #{to}" end # >> "梅田 - 淀屋橋" # >> "淀屋橋 - 本町" # >> "本町 - 心斎橋" # >> "心斎橋 - なんば"
- ふたつの値を、ふたつの変数で受け取る。
each_cons の例 (3)
a = [*0..3] # => [0, 1, 2, 3] a.each_cons(3){|i| p i} # >> [0, 1, 2] # >> [1, 2, 3] a.each_cons(3){|i, j| p [i, j]} # >> [0, 1] # >> [1, 2]
- 繰り返しの回数は、どちらも 2回。
- みっつの値を、ひとつの変数で受け取ると Array オブジェクトになる。(上)
- みっつの値を、ふたつの変数で受け取ると、みっつ目の値が受け取れない。(下)
おかしいな? と思ったら
p unknowns.first # >> [1, ["matz", 47]] unknowns.each do |id, (name, age)| id # => 1 name # => "matz" age # => 47 end
- 要素を、ひとつ取り出して見る。
- 例えば、こんなふうに表示されたら、値は幾つか?
- 答えは、ふたつ! *2
- 数値 (1)
- Array
- 中身は、文字列 ("matz') と数値 (47)
- 答えは、ふたつ! *2
- みっつの変数で受け取るには Array の中の Array を ( ) で表記する。
ブロックを受け取るメソッド
- こんな感じで呼びたい
monta{puts 'block!'} # >> block! # >> block! # >> 大切なことなので
- monta というメソッドを作る。
- ブロックを 2回評価して、
- 最後に '大切なことなので' と出力する。
ブロックを受け取る方法は、ふたつある。
ブロックを受け取る (2)
def monta &block block.call block.call puts '大切なことなので' end
- 引数でブロックを受け取る。
- 引数に & を付ける。
- call メソッドでブロックを評価する。
値を渡す
- monta メソッドの仕様を変更。
- ブロックに '大切なことなので' という文字列を渡す。
def monta yield '大切なことなので' yield '大切なことなので' end monta{|i| puts "#{i} block!"} # >> 大切なことなので block! # >> 大切なことなので block!
- yield メソッドに文字列を渡すだけの簡単なお仕事です。
値を渡す (2)
- また monta メソッドの仕様を変更。
- ブロックに '大切な', 'ことなので' という、ふたつの文字列を渡す。
def monta &block block.call '大切な', 'ことなので' block.call ['大切な', 'ことなので'] end monta{|i| puts "#{i} block!"} # >> 大切な block! # >> ["大切な", "ことなので"] block!
- block.call に、ふたつの文字列を渡すだけだと……
- ブロックがひとつの変数で待ち受けていると、ふたつ目の文字列が受け取ってもらえない。
- ふたつ以上の値を渡すときは、Array のオブジェクトにして渡す。
ブロックは Proc
block = Proc.new do |i, j| puts "#{i}#{j} block!" end monta &block # >> 大切なことなので block! # >> 大切なことなので block!
- あらかじめブロックだけ生成しておける。
- メソッドに渡す際には & を付ける。
Q&A
- Q1
- ブロックをふたつ渡せないの?
- A1
- & なしで普通の引数としてなら渡せます。
- Q2
- さっきから Emacs で Ruby のコード実行してるけど、それなに?
- A2
- るびきちさんが作られた rcodetools を使ってます。Ruby を起動して実行結果を # => の後に埋め込んでくれます。(コード補完もできます) コメントなので、結果の埋め込まれたコードは、そのまま保存・実行できます。
- Q3
- どんなキーバインドなの?
- A3
- えっ、それ重要?
*1:スライドだけ欲しい人は直接どうぞ http://higaki-it.jp/ruby/56/slide.pdf
*2:こんなデータ構造が Array で渡ってきたら、データ構造の設計が間違ってる。