社内ruby勉強会資料 #2

[第１回](https://qiita.com/sho0211/items/d5036008bd0638e4a95c)

# 今回のお題

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

# なぜこのお題を選んだか

## 対象は他言語経験者

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

## で、なんでこれ？

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

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

# ブロック

分類としてはメソッドを呼び出すときの[シンタックスの一つ](https://docs.ruby-lang.org/ja/2.5.0/doc/spec=2fcall.html#block)。
特別な機能を持っているわけではない。

## 見た目

### パターン１

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

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

### パターン２

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

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

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

### 注意事項

実は単なる見た目だけの違いだけではない。
評価時の結合度にも違いがあるので注意が必要。
[参考1](https://docs.ruby-lang.org/ja/2.5.0/doc/spec=2fcall.html#block)
[参考2](https://qiita.com/riocampos/items/43e4431ddff93e01a18d)

- ブロックを使うときには`()`を省略しない
- 複数行で評価される結果を使おうとしない[^readable]

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

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

## やってみよう

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

```ruby
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はイテレータが強力なので`for`や`while`を使う機会はほぼない
- 繰り返す内容はブロックで渡す

## 例

一緒にやってみよう

### n回の繰り返し

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

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

### ある範囲の値で

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

[^step]: [step](https://docs.ruby-lang.org/ja/2.5.0/method/Numeric/i/step.html)の方がスマートですね！ @scivolaさんありがとうございます

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

```ruby
%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`を起点とした様々な[便利メソッド群](https://docs.ruby-lang.org/ja/2.5.0/class/Enumerable.html)。
- 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 }`[^sort]を年代順に並べ替えよう（`sort_by`）
- `[0,"",Object.new,Class,Module,[],{}]`がすべて真偽値として真となるか（`all?`[^noarg]）
- `("Aaaaa".."Zzzzz")`の範囲に'Ateam'が現れるか（`any?`）
- [Enumerable](https://docs.ruby-lang.org/ja/2.5.0/class/Enumerable.html)のメソッドを`each`をもとにして自分で作ってみよう（暇な人用：車輪の再開発による理解）

<details><summary>サンプル</summary><div>

```ruby
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"}
```    

</div></details>

[^sort]: 現在のソート順を推測してはいけない
[^noarg]: 実はブロック使わなくてもかける

# 今回のまとめ

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