こんにちは、鈴木です。
Ruby2.0 の正式版リリースが迫って来ましたね!
リリース予定日は Ruby の 20 歳の誕生日、今年の 2/24 (日) です。
非常に待ち遠しいです。
開発中の Rails4.0 も Ruby2.0 推奨となりました。
こちらもリリースがいつになるのか、動向が気になるところです。
今回は Ruby2.0 の新機能、キーワード引数を取り上げます。
といっても、それほど難しいものではありません。
キーワード引数とは、メソッドの各引数に名前(キーワード)を付けて、それによって引数を指定することができる機能です。
オプショナルな引数にまつわる面倒事
オプショナルな引数を扱う一般的な方法は 2 つあります。一つはデフォルト引数を使用する方法、もう一つはオプショナルな引数をハッシュで受け渡しする方法です。
デフォルト引数
デフォルト引数を使用する場合の例です。
1 2 3 |
def welcome_message(message, name='ゲスト') "#{message}、#{name}さん" end |
上記の welcome_message は 「こんにちは、太郎さん」のような文字列を組み立てるメソッドです。
引数の name がオプショナルな引数であり、デフォルト値(引数を省略した場合の値)は 'ゲスト' です。
デフォルト引数を使用する場合のデメリットとして、
- 数が増えた場合に引数の順番を覚えることが面倒(引数の指定順を間違えやすい)。
- 3 番目の引数は明示的に指定したいが、2 番目の引数は省略(デフォルト値を使用)したい場合に 2 番目の引数も指定しなければならない。
があります。そこで考えられたアプローチが、オプショナルな引数をハッシュで受け渡しする方法です。
オプショナルな引数を保持するハッシュ
Rails で積極的に使用されている方法です。
1 2 3 4 5 6 |
def welcome_message(message, options={}) default_options = {name: 'ゲスト'} options = default_options.merge(options) # 無効なキーが渡された場合のチェックはどうしよう… "#{message}、#{options[:name]}さん" end |
上記のように、オプショナルな引数は options というハッシュで受け取ります。
メソッド内の「options = default_options.merge(options)」の部分では、省略された場合のデフォルト値をマージしています。
デフォルト引数を使用する場合の欠点が解決されている一方で、
- 指定可能な引数が options というハッシュに隠れてしまった(コメントに指定可能なキーが書かれていなければ実装を見るしかない)。
- 無効なキーが渡された場合のチェックをする/しない、という問題がある(チェックしたほうが親切だけれど、チェックするのは面倒)。
という新たな問題を孕んでいます。
キーワード引数を使う
キーワード引数を使うと、具体的にどのように変わるのか見ていきましょう。
メソッドの定義
Before: これが、
1 |
def welcome_message(message, options={}) |
After: こうなります。
1 |
def welcome_message(message, name: 'ゲスト') |
オプショナルな引数として何を取るのか、ぱっと見ただけで分かりますね。
メソッドの呼び出し
Before: これが、
1 |
welcome_message('こんちには', name: '太郎') |
After: こうなります。
1 |
welcome_message('こんにちは', name: '太郎') |
何も変わりません。
メソッドの実装
Before: オプショナルな引数を保持するハッシュとデフォルト値を持つハッシュをマージ。無効なキーが渡された場合はエラーとした方が親切だと思いつつ、チェックが面倒だという心の葛藤がある(結構な割合で葛藤に負ける)。これが、
1 2 3 4 5 6 7 8 9 10 11 |
# ウェルカムメッセージ (Ex. 'こんにちは、太郎さん') を生成する. def welcome_message(message, options={}) # デフォルト値とマージする. default_options = {name: 'ゲスト'} options = default_options.merge(options) # 許可していないキーが渡された場合のチェックは面倒なので省略しよう… # 表示用のメッセージを構築する. "#{message}、#{options[:name]}さん" end |
After: こうなります。
1 2 3 4 5 |
# ウェルカムメッセージ (Ex. 'こんにちは、太郎さん') を生成する. def welcome_message(message, name: 'ゲスト') # 表示用のメッセージを構築する. "#{message}、#{name}さん" end |
随分すっきりしました。
キーワード引数の特徴
キーワード引数の特徴をまとめます。
- 引数の順番を気にしなくて良い(ハッシュを使用する場合と同じ)。
- メソッドのシグネチャから得られる情報量が多い(ハッシュだと何を渡して良いのか分からない)。
- デフォルト値とマージする手間が無くなる(default_options.merge(options) という定型句が消える)。
- オプショナルな引数をチェックする手間が無くなる(無効なキーが渡された場合にエラーとなる)。
- キーワードの名前が変更された場合に、メソッドの呼び出し元も変更する必要がある(リファクタリング時の修正箇所が増える)。
メソッドに渡す最後の引数がハッシュの場合の問題
※2013/12/22 追記:Ruby-2.0.0-p247 で必須引数とキーワード引数の優先順位が変更され、以下の問題は解消しました。
以下のようにメソッドに渡す最後の引数がハッシュの場合は、ArgumentError となります。
1 2 3 4 5 6 7 |
def f(x, y, z: 777) ... end x = 'Hello' y = {:abc => 123} f(x, y) # => unknown keyword: abc (ArgumentError) |
これは、上記の y が単なるハッシュなのか、キーワードのためのハッシュなのか区別する方法が無いためです。
この動作は仕様とのことです。
上記ページでは、最後の引数に {} を追加するという対応策が提示されています。
つまり、以下のように記述すれば ArgumentError にはなりません。
1 |
f(x, y, {}) |
このような問題があることは頭の片隅に留めておきましょう。
Trackbacks
[...] キーワード引数 [...]