社内ruby勉強会資料 #2


第1回

今回のお題

  • ブロック
  • イテレータ
  • Enumerable

なぜこのお題を選んだか

対象は他言語経験者

一般の方に向けては説明不十分だったが、今回の対象は他言語の経験者のため、多くの言語に共通する概念は説明不要。
例えば、文字列って何? 配列って何? といった話は、すでにわかっているものとして進む。
オブジェクト指向についても、言語の色はありつつも大枠は共通認識を持っていると考えている。
rubyのオブジェクト指向については、後々説明していく。

で、なんでこれ?

  • rubyの特色がよく現れている
    • この辺を使いこなせば、ああruby書く人だなと感じられる
    • 訓練されたrubyistはfor/while/loopの構文を忘れている1
  • ブロックやイテレータはやはりとっつきにくい
    • js書いてればわかるかなとは思いつつも
  • 実際のコーディングで一番使い勝手が良い

ブロック

分類としてはメソッドを呼び出すときのシンタックスの一つ
特別な機能を持っているわけではない。

見た目

パターン1

obj.foo(arg){ |bar| puts bar.do_something } # {}で囲われている部分がブロック

ブロック内が短く、呼び出すメソッドと同じ行内で済む場合、こちらが用いられることが多い。

パターン2

obj.foo(arg) do |bar|
  baz = bar.do_something_with(other)
  baz.save!
end

ブロック内での処理が複数行に渡る場合はdo 〜 endを使うことが多い2

注意事項

実は単なる見た目だけの違いだけではない。
評価時の結合度にも違いがあるので注意が必要。
参考1
参考2

  • ブロックを使うときには()を省略しない
  • 複数行で評価される結果を使おうとしない3

あたりを気をつけていれば罠にハマることもないだろう。

やってみよう

余分なこともしつつ・・・

require 'tempfile'
tempfile = Tempfile.new
File.open(tempfile, 'w') do |f|
  f.puts "Learning ruby"
  f.puts "great language ruby"
end

content = File.read(tempfile)
p content.lines.map{|l| l.split.count}
  • splitはデフォルトで空白区切り
    • 楽だけど可読性を犠牲にしてる気もする(ほんの少しの驚きを与える)

イテレータ

  • 反復・繰り返し処理を行うための機構
    • 20年前に比べればかなり一般的な言葉になったね
    • XPでイテレーションって呼ぶよね
  • rubyはイテレータが強力なのでforwhileを使う機会はほぼない
  • 繰り返す内容はブロックで渡す

一緒にやってみよう

n回の繰り返し

5.times { puts "I love ruby" } # 洗脳中

# どういうことか確かめる
puts "------------------"
5.times { |i| printf("I love ruby%s(%d)\n", "!" * i, i) } # 更に洗脳中

ある範囲の値で

def schedule
  1.upto(9) do |n|
    puts " #{n}月 まだ大丈夫"
  end
  (10..12).each do |n|
    puts "#{n}月 頑張る"
  end
end
schedule
  • downtoもあるよ
  • whileの代わりとしてはFloat::INFINITY使うかなぁ4

コレクションに対する繰り返し処理

%w[string integer fixnum numeric bignum float array hash class module].each do |klass|
  klass_name = klass.gsub(/^(.)/){|c| c.upcase}
  puts klass_name + " < Object"
end

イテレータまとめ

  • イテレータはブロックを使って繰り返し処理するもの
  • (n..m)Rangeオブジェクトになるけど、(n...m)と境界条件を間違えるかも
  • 処理内容と意図が直観的にわかるものを採用しよう
  • each便利すぎるよ

Enumerable

  • eachを起点とした様々な便利メソッド群
  • ArrayとかHashとか、組み込みクラスにはもとからmix-inしているクラスが多い
  • 多くのメソッドが実務でもsilverでも頻出なので、使いながら覚えよう。

やってみよう

  • 1〜10の整数をすべて2乗する(mapまたはcollect)
  • 1〜10の整数で偶数のみ取得する(find_allまたはselect
  • 5000以上の整数の中で、13でも97でも割り切れる最初の数を取得する(Float::INFINITY使いつつfindまたはdetect
  • 1〜500まで全てかけた積(reduceまたはinject
  • 1〜500まで全て足した和(sum
  • 1〜500までの数を8で割った剰余のみを足した和(sum
  • {ruby: 1993, Elixir: 2012, golang: 2009, C: 1972, PHP: 1995 }5を年代順に並べ替えよう(sort_by
  • [0,"",Object.new,Class,Module,[],{}]がすべて真偽値として真となるか(all?6
  • ("Aaaaa".."Zzzzz")の範囲に'Ateam'が現れるか(any?
  • Enumerableのメソッドをeachをもとにして自分で作ってみよう(暇な人用:車輪の再開発による理解)

サンプル
p (1..10).map{|n| n**2}
p (1..10).select{|n| n.even?}
p 5000.upto(Float::INFINITY).detect{|n| n % (13*97) == 0}
p 1.upto(500).reduce{|n,m| n * m}
p (1..500).sum
p (1..500).sum{|n| n % 8}
p ({ruby: 1993, Elixir: 2012, golang: 2009, C: 1972, PHP: 1995 }).sort_by{|lang, year| year }
p [0,"",Object.new,Class,Module,[],{}].all?{|value| value ? true : false } # 真偽値を見たいことを明確にしてるだけ
p ("Aaaaa".."Zzzzz").any?{|text| text == "Ateam"}

今回のまとめ

  • ブロックはメソッドの呼び出し方の一つ
  • ブロックはよく繰り返し処理で使われるよ
    • それをイテレータと呼ぶ
  • Enumerableはイテレータを活用するモジュール
    • いろんなクラスで使われてる
    • 便利だから使おう。きっと使う。
    • でもEnumerableのメソッドということに気づかないでいるだろう
    • マニア的には内部イテレータによる拡張がEnumerableだよ
    • さらにマニア的にはect系と呼んだりする頻出メソッドがあるよ
  • 落とし穴がある!
    • findfind_allはRoRで使うと、ActiveRecordfind/find_allと区別しにくくなる(しかも奴はブロックを受け付けない!)
    • RoRの中でEnumerable#find/Enumerable#find_allを使いたいときにはdetect/selectを使おう

  1. loopは制御構文ではなくEnumaratorを産むメソッドでした。@scivolaさん、指摘ありがとうございます。 

  2. rubyは中括弧を極力使いたがらない印象が強い。Eiffelの影響だろうか。rubyに染まってしまえば、中括弧は読みづらいという感覚にもなる。 

  3. 可読性の面からも、複数行の結果を変数に突っ込むとかはやめておいたほうがいいだろう。 

  4. stepの方がスマートですね! @scivolaさんありがとうございます 

  5. 現在のソート順を推測してはいけない 

  6. 実はブロック使わなくてもかける