Ruby で is_a?
や ===
などで Array を要求するインターフェースを見ると、to_a
できるオブジェクトを渡したら暗黙的に to_a
してくれ~~~~という気持ちになることがよくあります(あるいは拡張ライブラリで Check_Type
してるときとか)。
class Color
def initialize(color)
@arr = color
end
# 文字列とか数値から色配列を作ったり
# 色配列から文字列とか数値を吐き出したりするスゴイ処理
def to_a
@arr
end
end
色配列をクラスで管理して、Array を要求されるシーンでは to_a で Array を取り出すようにしました。これ Array を要求されるたびに毎回 to_a
しないといけなくてつらみがあります。暗黙的に to_a
するインターフェースにしてくれ~~~~と思うことがよくあります。
でも実際には to_a
可能なオブジェクトと Array はべつものなので何でもかんでも暗黙的に to_a
するわけにはいかなかったりします。つらみ。
でもでもいちいち明示的に to_a
書くのはうつくしくないので何とかしたいです。
color = Color.from_int(0xff00ff)
nanka_array_hoshigaru_method(color.to_a)
この to_a
がにくい。
to_a
書かずに to_a
する方法があれば to_a
書かなくてもいいですね?
color = *Color.from_int(0xff00ff) #=> [255, 0, 255]
nanka_array_hoshigaru_method(color)
かいせつ
代入のときに右辺値に *
プレフィックスをつけると
- Array -> そのまま
- Range -> Array
- String -> [String]
みたいな感じで Array に変換されます。Array や Range を見ると to_a
してるのかな?(Array#to_a
は自身を返す)って思うんですが to_a
を持たないはずの String が要素数 1 の Array になっているあたりめっちゃ謎です。
見た感じ、
- to_a できるオブジェクト -> to_a
- to_a できないオブジェクト -> [to_a できないオブジェクト]
こんな動きをしているっぽい。
この推測が正しいのか確かめるため、われわれは南米アマゾンの奥地へと向かった。
Ruby のうちがわではどんなふうに書かれているのか
アマゾンの奥地なんか行かなくても Ruby のソースを見に行けば分かりますね……。
insns.def にそれらしい記述があります。insns.def というのはなんぞやというと VM の命令シーケンスの一覧が書かれたファイルです。VM の命令シーケンスはこんなふうな処理してますよということが書いてあるっぽい(たぶん……)。
insns.def/** @c put @e splat array @j 配列 ary に対して to_a を呼び出す。 */ DEFINE_INSN splatarray (VALUE flag) (VALUE ary) (VALUE obj) { VALUE tmp = rb_check_convert_type(ary, T_ARRAY, "Array", "to_a"); if (NIL_P(tmp)) { tmp = rb_ary_new3(1, ary); } else if (RTEST(flag)) { tmp = rb_ary_dup(tmp); } obj = tmp; }
to_a
できなかったら rb_ary_new
して to_a
できたら to_a
したものを返すよと書いてあります。
なので、代入時の splat 展開は
- to_a できるオブジェクト -> to_a
- to_a できないオブジェクト -> [to_a できないオブジェクト]
という実装になっていると思ってよさそうです。
あとがき
変数に格納するなら to_a したほうが明示的に配列作ってることが分かっていいので変なことしたくてエビデンス求めてアマゾンの奥地目指すの本末転倒なので素直に to_a しましょう。
余談
変数に格納しないでそのまま引数に渡したいときとかあると思います。
nanka_array_hoshigaru_method([*Color.from_int(0xff00ff)])
こういう書き方ができます。「to_a
するよりタイプ数が少なくて有利だゾ!」という甘言に惑わされることなく、読みやすいコードを書きましょう……。