Help us understand the problem. What is going on with this article?

Rubyのキーワード引数はシンボルっぽく定義するけど、シンボルそのものではない、という話

はじめに

拙著「プロを目指す人のためのRuby入門(通称チェリー本)」の読者さんの中で、ときどきキーワード引数について困惑している人を見かけるので、この記事でキーワード引数に関する説明を補足しておきます。

Ruby初心者さんの疑問:キーワード引数はシンボルじゃないの?

Ruby初心者さんの中には、キーワード引数をシンボルとして参照できないことに疑問を持つ方がおられるようです。

たとえばこんな感じです。(「プロを目指す人のためのRuby入門」5.4.3項のコード例を一部改変)

def buy_burger(menu, drink: true, potato: true)
  # なんで :drink じゃないの?
  if drink
    # ...
  end
  # なんで :potato じゃないの?
  if potato
    # ...
  end
end

なるほど、そう言われてみると、メソッド定義のキーワード引数はたしかにキーがシンボルのハッシュっぽい書き方になってますね・・・。

# drink: true, potato: true の部分だけ見ると、
# たしかにキーがシンボルのハッシュっぽい
def buy_burger(menu, drink: true, potato: true)

# 実際、こう書けばキーがシンボルのハッシュになる
hash = { drink: true, potato: true }
hash[:drink]  #=> true
hash[:potato] #=> true

でも、これは記法が似ているだけであって、シンボルではないんです。
あくまでメソッドの引数(仮引数)なんです。

ですので、上のメソッドのmenuと同じように、メソッド内ではdrinkpotatoのように参照します。

もし、:drink:potatoのように書いてしまうと、これはシンボルオブジェクト(シンボルリテラル)を書いたことになります。

def buy_burger(menu, drink: true, potato: true)
  # キーワード引数もメソッドの引数なので、menuと同様にdrinkと書く
  if drink
    # ...
  end

  # :drinkと書いた場合はメソッドの引数とは無関係な、ただのシンボルオブジェクトになってしまう
  if :drink
    # ...
  end

  # ...
end

とりあえず、この点については「キーワード引数はシンボルではないんです。定義するときの書き方はたしかに似てるけど、そういうもんなんです」としか説明のしようがありません。

なぜキーワード引数をシンボルっぽく書くのか?その歴史的経緯

とはいえ、キーワード引数をシンボルっぽく書くのは、おそらく歴史的な経緯があると考えています。

プロを目指す人のためのRuby入門」の5.6.3項にも書いたように、キーワード引数が導入される前(つまりRuby 1.9以前)はハッシュを使って擬似的にキーワード引数を実現していました。

# ハッシュを引数として受け取り、疑似キーワード引数を実現する
# (キーワード引数が導入される前はこう書くしかなかった)
def buy_burger(menu, options = {})
  # 引数のハッシュから目的の値をローカル変数に格納する
  drink = options[:drink]
  potato = options[:potato]

  # 引数から取り出した値を使って処理を書く
  if drink
    # ...
  end

  # ...
end

# このメソッド呼び出しではキーワード引数ではなく、
# { drink: true, potato: true } というハッシュオブジェクトを
# 引数として渡している
buy_burger('cheese', drink: true, potato: true)

このようにRubyの世界では昔からハッシュとキーワード引数が近しい関係にあったため、キーワード引数の定義の仕方もハッシュの記法に似たものになったのだろう、と僕は考えています。

# 昔はみんなハッシュで書いてたんだから・・・
def buy_burger(menu, options = {})
  # ...
end

# キーワード引数もハッシュっぽい記法になっていると自然(?)
def buy_burger(menu, drink: true, potato: true)
  # キーワード引数にすると、このローカル変数が引数に置きかわるイメージ
  # drink = options[:drink]
  # potato = options[:potato]

  if drink
    # ...
  end

  # ...
end

# ちなみに、呼び出し方はどちらも変わりません
buy_burger('cheese', drink: true, potato: true)

ただ、この歴史的経緯を知っている人にとっては「うん、自然だね」と思えるものの、そうじゃない人にとっては「えっ、なんでシンボルじゃないの!?」とビックリする原因になるのかもしれませんね。

まとめ

というわけで、この記事では「Rubyのキーワード引数はシンボルっぽく定義するけど、シンボルそのものではない」という話を説明しました。

プロを目指す人のためのRuby入門」の改訂版を出す機会があれば、この内容も追加しておこうと思います。
それまではこの記事を参考にしてもらえると幸いです。

あわせて読みたい

Ruby 2.0でキーワード引数が導入されたときに書かれた記事です。
上で説明した「歴史的経緯」をより詳しく理解することができます。

一方で、この「歴史的経緯」が原因で、Ruby本体のややこしい不具合の原因になってしまったという問題もあります。
そのため、Ruby 2.7以降ではキーワード引数の扱いが変更されています。
詳しくはこちらの記事をご覧ください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away