HashWithIndifferentAccess は Hash と違いキーワード引数に直接渡せない

# 追記

Ruby 2.6 からはキーワード引数にシンボル以外のキーを渡すことができなくなるようです, 自然な流れな気がしています.

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">mrubyのキーワード引数を実装してたらRubyのキーワード引数に謎の挙動を「発見」した。</p>&mdash; Yukihiro Matsumoto (@yukihiro_matz) <a href="https://twitter.com/yukihiro_matz/status/1022287578995646464?ref_src=twsrc%5Etfw">2018年7月26日</a></blockquote>

> Non-Symbol key in keyword arguments hash causes an exception.

https://github.com/ruby/ruby/blob/1692ccaf9f49c001b18de8b7296ede3f68190ab8/NEWS より

# `HashWithIndifferentAccess`

Rails (activesupport) で提供されている `Hash` のサブクラス
特徴として、 `Symbol` と `String` を区別せず同一のキーとして取り扱う。

```rb
h1 = Hash.new
h1[:foo] = 1
h1[:bar] = 2
h1['bar'] = 3
puts h1[:foo]  # => 1
puts h1['foo'] # => nil
puts h1[:bar]  # => 2
puts h1['bar'] # => 3

h2 = HashWithIndifferentAccess.new
h2[:foo] = 1
h2[:bar] = 2
h2['bar'] = 3
puts h2[:foo]  # => 1
puts h2['foo'] # => 1
puts h2[:bar]  # => 3
puts h2['bar'] # => 3
```

`HashWithIndifferentAccess` の内部的な話として、キーに対して `Symbol` でアクセスが来た際は `String` に丸めている。

```rb
h3 = HashWithIndifferentAccess.new
h3[:foo] = 1
h3['bar'] = 2
h3[:baz] = 3

# Symbol は String に丸められている
h3.keys # => ["foo", "bar", "baz"]
```

# キーワード引数

Ruby 2.0.0 からの機能。メソッドを定義する際、以下のように記述できる。

```rb
def test foo: 1, bar: 2, baz: 3
  [foo, bar, baz].join(',')
end
```

呼び出す際は、以下のような感じ。
以下の、「ハッシュを直接渡すこともできる」の部分が **今回のキモ**

```rb
# 省略した箇所は、定義中のデフォルトが与えられる
test                           # => '1,2,3'
test foo: 4                    # => '4,2,3'
test foo: 4, bar: 5, baz: 6    # => '4,5,6'

# ハッシュを直接渡すこともできる
arg = {foo: 7, bar: 8, baz: 9}
test arg                       # => '7,8,9'
```

# キーワード引数に `HashWithIndifferentAccess` を与えてみる

```rb
arg = HashWithIndifferentAccess.new({foo: 4, bar: 5, baz: 6})
test arg # => ArgumentError: wrong number of arguments (1 for 0)
```

- キーワード引数が受け入れ可能なハッシュは、全てのキーが `Symbol` であることが前提  
  参考 ⇒ `class.c` の [rb_extract_keywords](https://github.com/ruby/ruby/blob/v2_2_2/class.c#L1905) (と [separate_symbol](https://github.com/ruby/ruby/blob/v2_2_2/class.c#L1894) あたり)
- しかしながら、 `HashWithIndifferentAccess` はキーを `String` で保持しているのでキーワード引数の要件を満たさない

# ちなみに確認

```rb
arg = {foo: 4, bar: 5, "baz" => 6}
test arg # => ArgumentError: wrong number of arguments (1 for 0)
```

`Hash` であっても同様で、キーが全て `Symbol` でなければキーワード引数として受け付けてくれない。

# そもそもエラーメッセージの `ArgumentError` って何言ってんのよ？

**追記: 以下の挙動は Ruby 2.5 までのものです.**

`ArgumentError: wrong number of arguments (1 for 0)`
[rb_extract_keywordsの説明](https://github.com/ruby/ruby/blob/v2_2_2/README.EXT.ja#L1322) より

>   original_hashで参照されるHashオブジェクトから，Symbolである
>   キーとその値を新しいHashに取り出します．original_hashの指す
>   先には，元のHashがSymbol以外のキーを含んでいた場合はそれらが
>   コピーされた別の新しいHash，そうでなければ0が保存されます．

つまり、キーが全て Symbol でないハッシュを引数に与えると...

```rb
arg = {foo: 4, bar: 5, "baz" => 6}
test arg
```

以下のような呼び出しと扱われる。

```rb
test({"baz" => 6}, foo: 4, bar: 5)
```

(キーワード引数以外の) 引数が 1つ与えられる形になるので
先のエラー (0個引数を与えるべき箇所に1つの引数が渡っている) になる。

```rb
# 第一引数を用意しておくと...
def test1 x, foo: 1, bar: 2, baz: 3
  [foo, bar, baz, x].join(',')
end

# 第一引数に arg 全てがわたり、キーワード引数に {} が渡される。
arg = HashWithIndifferentAccess.new({foo: 4, bar: 5, baz: 6})
test1 arg # => "1,2,3,{\"foo\"=>4, \"bar\"=>5, \"baz\"=>6}"
```