【bugs.ruby Advent Calender】引数が渡されなかったことを判定する【1日目】
bugs.ruby Advent Calender 1日目の記事です。
これはなに
今年1年間通してみてきた bugs.ruby のチケットの中から気になったものを1つずつ取り上げていく Advent Calender です。
取り上げるチケットは基本的にこのブログで取り上げたものになります。
記事のまとめは ここを参照 してください。
[Feature #20326] Add an undefined
for use as a default argument.
メソッドで『引数を受け取るんだけど引数がなかった場合に特別な処理を行いたい』みたいなことが稀にあります。
その場合、例えばデフォルト引数を nil
にして nil
かどうかで判定することが多いと思います。
class X
def any_method(item = nil)
if item.nil?
raise "引数がありません!"
end
# ...
end
end
ただ、上記の場合では x.any_method(nil)
と x.any_method()
の呼び出しが区別できずに困ることもあります。
なので厳密な意味で『引数がないこと』を判定する場合は nil
以外の一意となるオブジェクトを使用することで回避することができます。
class X
# 『未定義用』のオブジェクトを生成しておく
UNDEFINED = Object.new
private_constant :UNDEFINED
def any_method(item = UNDEFINED)
if item == UNDEFINED
raise "引数がありません!"
end
# ...
end
end
# no error
pp X.new.any_method(nil)
# error: 引数がありません! (RuntimeError)
pp X.new.any_method()
このチケットでは上記の UNDEFINED
相当の機能を Ruby の本体に組み込む提案になります。
具体的には以下のように利用できる機能を提案しています。
# Kernel に #undefined と #undefined? の定義を追加する
module Kernel
UNDEFINED = Object.new
def UNDEFINED.inspect = -'undefined'
UNDEFINED.freeze
private_constant :UNDEFINED
def undefined? = self == UNDEFINED
module_function
def undefined = UNDEFINED
end
class X
# #undefined と undefined? を利用してそれが未定義なオブジェクトかどうかを判定する
def any_method(item = undefined)
if item.undefined?
raise "引数がありません!"
end
# ...
end
end
これだけみるとなんかよさそうな雰囲気はあるのですがコメントでは『引数として undefined
が渡せてしまう』と指摘されています。
# error: 引数がありません! (RuntimeError)
pp X.new.any_method(undefined)
また JavaScript の null
undefined
と同じような問題になるともコメントされていますねー。
あと個人的に目からウロコだったのが以下のように簡略的にかけると提示されています。
class X
def any_method(item = (item_not_set = true))
if item_not_set
raise "引数がありません!"
end
# ...
end
end
# no error
pp X.new.any_method(nil)
# error: 引数がありません! (RuntimeError)
pp X.new.any_method()
これはどういう動きをしているのかというと any_method
に対してなにか引数が渡されると (item_not_set = true)
の部分は評価されません。
評価されないとどういう状態になるのかというと item_not_set
というローカル変数は定義されているのですが値が未割り当ての状態なので item_not_set
の値は nil
になります。
class X
def any_method(item = (item_not_set = true))
{ item:, item_not_set: }
end
end
pp X.new.any_method(42)
# => {:item=>42, :item_not_set=>nil}
一方で any_method
に対して引数を渡さなかった場合には (item_not_set = true)
が評価されるので item_not_set
の値は true
になります。
class X
def any_method(item = (item_not_set = true))
{ item:, item_not_set: }
end
end
pp X.new.any_method()
# => {:item=>42, :item_not_set=>true}
これは Ruby のことを熟知しているような使い方で芸術点が高いですね。
それ以外にもいくつかアイディアがコメントに書いてあるので気になる人はチケットの内容を読んでみるとよいと思います。
Discussion