each_with_indexには引数を渡せない、わけではない

## はじめに：Rubyで添え字付きでループを回し、かつ0以外の数値で開始する方法

配列や範囲（range）をループさせながら、添え字（インデックス）も同時に取得したい場合は`each_with_index`を使います。

以下は「[プロを目指す人のためのRuby入門](http://gihyo.jp/book/2017/978-4-7741-9397-7)」の4.8.1項で使用したサンプルコードです。

```ruby
fruits = ['apple', 'orange', 'melon']
# ブロック引数のiには0、1、2・・・と要素の添え字が入る
fruits.each_with_index { |fruit, i| puts "#{i}: #{fruit}" }
#=> 0: apple
#   1: orange
#   2: melon
```

`each_with_index`では0から添え字が始まりますが、要件によっては0以外の数値から始めたい場合もあります。その場合は`each_with_index`の代わりに`each.with_index`（`each` + `with_index`）を使います。

以下は「プロを目指す人のためのRuby入門」の4.8.3項で使用したサンプルコードです。

```ruby
fruits = ['apple', 'orange', 'melon']

# eachで繰り返しつつ、1から始まる添え字を取得する
fruits.each.with_index(1) { |fruit, i| puts "#{i}: #{fruit}" }
#=> 1: apple
#   2: orange
#   3: melon
```

人によっては「いや、そうじゃなくて`each_with_index(1)`って書きたいんだけど！」と思うかもしれません。ですが、実際にそんなコードを書くとエラーになります。

```ruby
fruits = ['apple', 'orange', 'melon']
fruits.each_with_index(1) { |fruit, i| puts "#{i}: #{fruit}" }
#=> ArgumentError (wrong number of arguments (given 1, expected 0))
```

そのため、「プロを目指す人のためのRuby入門」では「`each_with_index`メソッドには引数を渡せない」と書いていました（4.8.3項参照）。

が！

`each_with_index`に引数を渡してもエラーにならないケースがあります。
たとえば次のようなコードを書いた場合は、`each_with_index`に引数を渡してもエラーになりません。

```ruby
require "stringio"
sio = StringIO.new('apple,orange,melon')
# each_with_indexの引数を渡してもエラーにならない！
sio.each_with_index(',') {|fruit, i| puts "#{i}: #{fruit}" }
#=> 0: apple,
#   1: orange,
#   2: melon
```

なぜ`each_with_index`に引数を渡せる場合と渡せない場合があるのでしょうか？

## `each_with_index`に引数を渡すと、そのまま`each`メソッドに渡される

実は`each_with_index`に引数を渡すと、内部的にレシーバ（`each_with_index`を呼びだしたオブジェクト）の`each`メソッドにその引数が渡されます。

```ruby
fruits = ['apple', 'orange', 'melon']
# 内部的にfruits.each(1)が呼ばれる（エラー）
fruits.each_with_index(1) { |fruit, i| puts "#{i}: #{fruit}" }
```

```ruby
require "stringio"
sio = StringIO.new('apple,orange,melon')
# 内部的にsio.each(',')が呼ばれる（OK）
sio.each_with_index(',') {|fruit, i| puts "#{i}: #{fruit}" }
```

そして、クラスによっては`each`メソッドに引数を渡せるものがあります。
たとえば、先ほど出てきたStringIOクラスは`each`メソッドに行の区切り文字を引数として渡せるようになっています（省略した場合は改行区切りになります。ドキュメントは[こちら](https://docs.ruby-lang.org/ja/latest/class/StringIO.html#I_EACH)です）。

そのため、レシーバの`each`メソッドに引数が渡せるかどうかによって、`each_with_index`メソッドに引数が渡せるか渡せないかが決まります。

## 結局`each_with_index`単体では添え字の開始値を指定できない

`each_with_index`メソッドに引数を渡せるケースと渡せないケースがあることはわかりましたが、そもそもやりたかったのは`fruits.each_with_index(1)`のように、添え字の開始値を指定することでした。

ですが、「`each_with_index`メソッドに引数を渡した場合は`each`メソッドに引数が渡される」という仕様になっているため、結局`each_with_index`メソッドで添え字の開始値を指定することはできません。
なので、必ず`each.with_index(1)`のように、`each` + `with_index`の形で書く必要があります。

## おまけ：モンキーパッチで`each_with_index`の挙動を変えてしまう💀

「`each`メソッドに引数を渡すようなケースなんて滅多にないから、なんとかして`each_with_index`に直接添え字の開始値を指定したい！」という場合は、次のようなモンキーパッチを書いて`each_with_index`の挙動を変えてしまうこともできます。

```ruby
# 注意：こんなモンキーパッチはあまりお勧めしない
module Enumerable
  # オリジナルの実装を上書きして引数の仕様を変えてしまう
  def each_with_index(offset = 0, &b)
    each.with_index(offset, &b)
  end
end
fruits = ['apple', 'orange', 'melon']
# 添え字の開始値が直接指定できるようになった
fruits.each_with_index(1) { |fruit, i| puts "#{i}: #{fruit}" }
#=> 1: apple
#   2: orange
#   3: melon
```

ただし、自分で書いたコードはこれでよくても、ライブラリ等の自分の見知らぬ場所で引数付きの`each_with_index`が使われていると、整合性が合わなくなってエラーが起きる可能性があります。なので、これはあまり好ましい対応ではありません。

単純なモンキーパッチではなく、refinementsを使うとそのファイル内（もしくはクラス内）だけに影響範囲を留めることができます。

```ruby
# 以下のコードはirbではなくファイルに保存して実行する
module MyEachWithIndex
  refine Enumerable do
    def each_with_index(offset = 0, &b)
      each.with_index(offset, &b)
    end
  end
end
using MyEachWithIndex

fruits = ['apple', 'orange', 'melon']
# このファイル内でのみ、添え字の開始値が直接指定できる
fruits.each_with_index(1) { |fruit, i| puts "#{i}: #{fruit}" }
```

が、個人的にはそこまで頑張らなくても、素直に`each.with_index(1)`と書く方が良いと思います。

なお、モンキーパッチやrefinementsについては「プロを目指す人のためのRuby入門」の中でも説明しています（7.10.6項および8.9.5項参照）。

## まとめ
というわけで、この記事では`each_with_index`メソッドで引数を指定できるケースがあることを説明しました。

ただし、日常的なプログラミングで`each_with_index`メソッドに引数を渡す（つまり、`each`メソッドに引数を渡す）ケースはあまりないと思います。
ですので、「`each_with_index`メソッドには引数を渡せない」と覚えていてもほとんど問題ないと思いますが、例外的なケースがあることも知っておくと、どこかで役に立つかもしれません。

### 参考文献

実は僕自身も@znzさんのこちらのコメントを読むまでは、`each_with_index`に引数が渡せることを知りませんでした😅

- https://qiita.com/keisukegdk/items/6a271748ab517723ae0c#comment-f4112363d93d622bce38

今回の記事は、@znzさんが上記のコメントの中で紹介していたこちらの議論を、僕なりにかみ砕いてまとめた内容になっています。

- [Feature \#9140: Allow each\_with\_index to get start index \- Ruby trunk \- Ruby Issue Tracking System](https://bugs.ruby-lang.org/issues/9140)

`each_with_index`に引数が渡せることは、英語版のAPIドキュメントで説明されています（2018年8月時点では、[日本語版](https://docs.ruby-lang.org/ja/latest/class/Enumerable.html#I_EACH_WITH_INDEX)には載っていません）。

- https://ruby-doc.org/core-2.5.1/Enumerable.html#method-i-each_with_index

> each_with_index(\*args) { |obj, i| block } → enum
> each_with_index(\*args) → an_enumerator
>
> Calls block with two arguments, the item and its index, for each item in enum. Given arguments are passed through to each().