Actions
Feature #14869
openProposal to add Hash#===
Status:
Open
Assignee:
-
Target version:
-
Description
概要¶
Hash#===
を追加する提案になります。
仕様¶
レシーバのキーの要素と引数のハッシュのキーの要素を #===
で比較して、全てが真なら true
を返し、そうでないなら false
を返す。
また、レシーバが空のハッシュの場合、引数が空のハッシュなら true
を返し、そうでないなら false
を返す。
user = { id: 1, name: "homu", age: 14 }
# name 要素が data にあるので true
p ({ name: "homu" } === user)
# => true
# 複数の要素があっても OK
p ({ id: 1, name: "homu", age: 14 } === user)
# => true
# name 要素が user にあるが、値が違うので false
p ({ name: "name" } === user)
# => false
# キーの要素が引数にないので false
p ({ number: 42 } === user)
# => false
# 1つでもキーの要素がない場合も false
p ({ id: 1, name: "homu", number: 42 } === user)
# => false
# レシーバが空のハッシュなら false
p ({} == user)
# => false
# 空のハッシュ同士なら true
p ({} == {})
# => true
# 引数がハッシュ以外なら false
p ({ id: 42 } == 42)
# => false
# #=== で比較しているのでこういうこともできる
p ({ name: /^h/ } === user)
# => true
p ({ age: (1..20) } === user)
# => true
p ({ age: Integer } === user)
# => true
ユースケース¶
バリデーション¶
case-when では ===
を使用して値を比較しているので、Hash#===
を利用することで次のように条件分岐を行うことが出来る。
def validation user
case user
# name に対するバリデーション
when { name: /^[a-z]/ }
raise "名前の先頭が小文字の場合は登録できません"
# age に対するバリデーション
when { age: (0..20) }
raise "0〜20歳は登録できません"
# 各要素が任意のクラスのインスタンスかどうかのバリデーション
when { id: Integer, name: String, age: Integer }
true
else
false
end
end
# 条件を満たしているので OK
mami = { id: 1, name: "Mami", age: 21 }
validation mami
# => true
# name が小文字から始まっているので NG
mado = { id: 2, name: "mado", age: 13 }
validation mado
# => 名前の先頭が小文字の場合は登録できません (RuntimeError)
# age が 0〜20歳以内なので NG
homu = { id: 3, name: "Homu", age: 14 }
validation homu
# => 0〜20歳は登録できません (RuntimeError)
Enumerable#grep
¶
Enumerable#grep
は内部で ===
を使用した比較を行っているので、次のように任意の Hash のキーの要素に対して検索を行うことが出来る。
data = [
{ id: 1, name: "Homu", age: 13 },
{ id: 2, name: "mami", age: 14 },
{ id: 3, name: "Mado", age: 21 },
{ id: 4, name: "saya", age: 14 },
]
# 特定の要素が含まれている Hash のみを絞り込む
p data.grep(name: /m/)
# => [{:id=>1, :name=>"Homu", :age=>13}, {:id=>2, :name=>"mami", :age=>14}]
p data.grep(age: (1..20))
# => [{:id=>1, :name=>"Homu", :age=>13}, {:id=>2, :name=>"mami", :age=>14}, {:id=>4, :name=>"saya", :age=>14}]
補足1: ==
ではなくて ===
で比較する理由¶
-
===
を使用することでより細かい・抽象的な条件を指定することが出来る-
Class
やRegexp
、Proc
などで比較することが出来る
-
- 内部で
===
を使用している場合、==
で比較したい場合はobj.method(:==)
を渡せば実現出来るが、その逆は出来ない- 内部で
==
を使用している場合、===
で比較ししたくても出来ない
- 内部で
補足2: 空のハッシュの比較に関して¶
-
Object#===
の場合だと{} === 42
が例外ではなくてfalse
を返していたので、Hash#===
もfalse
を返すようにした-
{} === {}
がtrue
を返すのも同様の理由になります - これにより以下のような既存のコードも互換性を壊すことなく動作するかと思います
-
def check n
case n
when {}
"Empty Hash"
when []
"Empty Array"
when 0
"Zero"
else
"Not empty"
end
end
p check({}) # => "Empty Hash"
p check([]) # => "Empty Array"
p check(0) # => "Zero"
p check({ name: "mado" }) # => "Not empty"
以上、Hash#===
に関する提案になります。
挙動に関して疑問点や意見などございましたらコメント頂けると助かります。
Files
Actions
Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0