模式比對¶ ↑
模式比對是一種允許深入比對結構化值的功能:檢查結構並將比對的部分繫結到局部變數。
Ruby 中的模式比對是使用 case
/in
表達式實作的
case <expression> in <pattern1> ... in <pattern2> ... in <pattern3> ... else ... end
(請注意,in
和 when
分支不能在一個 case
表達式中混合使用。)
或使用 =>
算子和 in
算子,它們可用於獨立表達式
<expression> => <pattern> <expression> in <pattern>
case
/in
表達式是窮舉的:如果表達式的值與 case
表達式的任何分支都不匹配(且沒有 else
分支),則會引發 NoMatchingPatternError
。
因此,case
表達式可用於條件比對和解壓縮
config = {db: {user: 'admin', password: 'abc123'}} case config in db: {user:} # matches subhash and puts matched value in variable user puts "Connect with user '#{user}'" in connection: {username: } puts "Connect with user '#{username}'" else puts "Unrecognized structure of config" end # Prints: "Connect with user 'admin'"
當預期的資料結構事先已知,且僅需解開其中部分時,=>
算子最為有用
config = {db: {user: 'admin', password: 'abc123'}} config => {db: {user:}} # will raise if the config's structure is unexpected puts "Connect with user '#{user}'" # Prints: "Connect with user 'admin'"
<expression> in <pattern>
與 case <expression>; in <pattern>; true; else false; end
相同。當您僅想知道是否已比對某個樣式時,可以使用它
users = [{name: "Alice", age: 12}, {name: "Bob", age: 23}] users.any? {|user| user in {name: /B/, age: 20..} } #=> true
請參閱下方以取得更多範例和語法說明。
樣式¶ ↑
樣式可以是
-
任何 Ruby 物件(由
===
算子比對,例如在when
中);(值樣式) -
陣列樣式:
[<子樣式>, <子樣式>, <子樣式>, ...]
;(陣列樣式) -
尋找樣式:
[*變數, <子樣式>, <子樣式>, <子樣式>, ..., *變數]
;(尋找樣式) -
雜湊樣式:
{key: <子樣式>, key: <子樣式>, ...}
;(雜湊樣式) -
使用
|
組合樣式;(替代樣式) -
變數擷取:
<樣式> => 變數
或變數
;(作為樣式,變數樣式)
任何樣式都可以在指定 <子樣式>
的陣列/尋找/雜湊樣式中巢狀。
Array
樣式和尋找樣式比對陣列,或回應 deconstruct
的物件(請參閱下方關於後者的說明)。Hash
樣式比對雜湊,或回應 deconstruct_keys
的物件(請參閱下方關於後者的說明)。請注意,雜湊樣式僅支援符號鍵。
陣列和雜湊樣式行為之間的一個重要差異是,陣列僅比對整個陣列
case [1, 2, 3] in [Integer, Integer] "matched" else "not matched" end #=> "not matched"
而雜湊即使在指定部分之外還有其他鍵,也能比對
case {a: 1, b: 2, c: 3} in {a: Integer} "matched" else "not matched" end #=> "matched"
{}
是此規則的唯一例外。它僅在給定空雜湊時比對
case {a: 1, b: 2, c: 3} in {} "matched" else "not matched" end #=> "not matched" case {} in {} "matched" else "not matched" end #=> "matched"
還有一種方法可以指定,除了樣式明確指定的那些之外,比對的雜湊中不應有其他鍵,方法是使用 **nil
case {a: 1, b: 2} in {a: Integer, **nil} # this will not match the pattern having keys other than a: "matched a part" in {a: Integer, b: Integer, **nil} "matched a whole" else "not matched" end #=> "matched a whole"
陣列和雜湊樣式都支援「剩餘」規格
case [1, 2, 3] in [Integer, *] "matched" else "not matched" end #=> "matched" case {a: 1, b: 2, c: 3} in {a: Integer, **} "matched" else "not matched" end #=> "matched"
兩種樣式周圍的括號都可以省略
case [1, 2] in Integer, Integer "matched" else "not matched" end #=> "matched" case {a: 1, b: 2, c: 3} in a: Integer "matched" else "not matched" end #=> "matched" [1, 2] => a, b [1, 2] in a, b {a: 1, b: 2, c: 3} => a: {a: 1, b: 2, c: 3} in a:
Find
樣式類似於陣列樣式,但可用於檢查給定物件是否有任何與樣式比對的元素
case ["a", 1, "b", "c", 2] in [*, String, String, *] "matched" else "not matched" end
變數繫結¶ ↑
除了深入結構檢查之外,樣式比對的一項非常重要的功能是將比對的部分繫結到區域變數。繫結的基本形式是在比對的(子)樣式之後指定 => variable_name
(有人可能會發現這類似於在 rescue ExceptionClass => var
子句中將例外儲存在區域變數中)
case [1, 2] in Integer => a, Integer "matched: #{a}" else "not matched" end #=> "matched: 1" case {a: 1, b: 2, c: 3} in a: Integer => m "matched: #{m}" else "not matched" end #=> "matched: 1"
如果不需要額外檢查,僅將資料的某一部分繫結到變數,可以使用更簡單的形式
case [1, 2] in a, Integer "matched: #{a}" else "not matched" end #=> "matched: 1" case {a: 1, b: 2, c: 3} in a: m "matched: #{m}" else "not matched" end #=> "matched: 1"
對於雜湊樣式,甚至存在更簡單的形式:僅鍵規格(沒有任何子樣式)也會將區域變數繫結到鍵的名稱
case {a: 1, b: 2, c: 3} in a: "matched: #{a}" else "not matched" end #=> "matched: 1"
Binding
也適用於巢狀樣式
case {name: 'John', friends: [{name: 'Jane'}, {name: 'Rajesh'}]} in name:, friends: [{name: first_friend}, *] "matched: #{first_friend}" else "not matched" end #=> "matched: Jane"
模式的「rest」部分也可以繫結到變數
case [1, 2, 3] in a, *rest "matched: #{a}, #{rest}" else "not matched" end #=> "matched: 1, [2, 3]" case {a: 1, b: 2, c: 3} in a:, **rest "matched: #{a}, #{rest}" else "not matched" end #=> "matched: 1, {:b=>2, :c=>3}"
Binding
目前無法對使用 |
連接的替代模式繫結變數
case {a: 1, b: 2} in {a: } | Array "matched: #{a}" else "not matched" end # SyntaxError (illegal variable in alternative pattern (a))
以 _
開頭的變數是此規則的唯一例外
case {a: 1, b: 2} in {a: _, b: _foo} | Array "matched: #{_}, #{_foo}" else "not matched" end # => "matched: 1, 2"
建議不要重複使用繫結值,因為此模式的目的是表示捨棄的值。
變數固定¶ ↑
由於變數繫結功能,現有的區域變數無法直接用作子模式
expectation = 18 case [1, 2] in expectation, *rest "matched. expectation was: #{expectation}" else "not matched. expectation was: #{expectation}" end # expected: "not matched. expectation was: 18" # real: "matched. expectation was: 1" -- local variable just rewritten
針對此情況,可以使用固定運算子 ^
,告訴 Ruby「將此值用作模式的一部分」
expectation = 18 case [1, 2] in ^expectation, *rest "matched. expectation was: #{expectation}" else "not matched. expectation was: #{expectation}" end #=> "not matched. expectation was: 18"
變數固定的重要用法之一是指定同一個值應在模式中出現多次
jane = {school: 'high', schools: [{id: 1, level: 'middle'}, {id: 2, level: 'high'}]} john = {school: 'high', schools: [{id: 1, level: 'middle'}]} case jane in school:, schools: [*, {id:, level: ^school}] # select the last school, level should match "matched. school: #{id}" else "not matched" end #=> "matched. school: 2" case john # the specified school level is "high", but last school does not match in school:, schools: [*, {id:, level: ^school}] "matched. school: #{id}" else "not matched" end #=> "not matched"
除了固定區域變數之外,您還可以固定執行個體、全域和類別變數
$gvar = 1 class A @ivar = 2 @@cvar = 3 case [1, 2, 3] in ^$gvar, ^@ivar, ^@@cvar "matched" else "not matched" end #=> "matched" end
您也可以使用括號固定任意表達式的結果
a = 1 b = 2 case 3 in ^(a + b) "matched" else "not matched" end #=> "matched"
比對非原始物件:deconstruct
和 deconstruct_keys
¶ ↑
如上所述,除了文字陣列和雜湊之外,陣列、find 和雜湊模式會嘗試比對任何實作 deconstruct
(針對陣列/find 模式)或 deconstruct_keys
(針對雜湊模式)的物件。
class Point def initialize(x, y) @x, @y = x, y end def deconstruct puts "deconstruct called" [@x, @y] end def deconstruct_keys(keys) puts "deconstruct_keys called with #{keys.inspect}" {x: @x, y: @y} end end case Point.new(1, -2) in px, Integer # sub-patterns and variable binding works "matched: #{px}" else "not matched" end # prints "deconstruct called" "matched: 1" case Point.new(1, -2) in x: 0.. => px "matched: #{px}" else "not matched" end # prints: deconstruct_keys called with [:x] #=> "matched: 1"
將 keys
傳遞給 deconstruct_keys
,以便在比對的類別中進行最佳化:如果計算完整的雜湊表示很昂貴,則可能只計算必要的子雜湊。當使用 **rest
模式時,會將 nil
傳遞為 keys
值
case Point.new(1, -2) in x: 0.. => px, **rest "matched: #{px}" else "not matched" end # prints: deconstruct_keys called with nil #=> "matched: 1"
此外,在比對自訂類別時,預期的類別可以指定為模式的一部分,並使用 ===
進行檢查
class SuperPoint < Point end case Point.new(1, -2) in SuperPoint(x: 0.. => px) "matched: #{px}" else "not matched" end #=> "not matched" case SuperPoint.new(1, -2) in SuperPoint[x: 0.. => px] # [] or () parentheses are allowed "matched: #{px}" else "not matched" end #=> "matched: 1"
這些核心和函式庫類別實作了解構
防護子句¶ ↑
當模式比對時,可以使用 if
附加額外的條件(防護子句)。此條件可以使用繫結變數
case [1, 2] in a, b if b == a*2 "matched" else "not matched" end #=> "matched" case [1, 1] in a, b if b == a*2 "matched" else "not matched" end #=> "not matched"
unless
也可以使用
case [1, 1] in a, b unless b == a*2 "matched" else "not matched" end #=> "matched"
附錄 A. 模式語法¶ ↑
近似語法為
pattern: value_pattern | variable_pattern | alternative_pattern | as_pattern | array_pattern | find_pattern | hash_pattern value_pattern: literal | Constant | ^local_variable | ^instance_variable | ^class_variable | ^global_variable | ^(expression) variable_pattern: variable alternative_pattern: pattern | pattern | ... as_pattern: pattern => variable array_pattern: [pattern, ..., *variable] | Constant(pattern, ..., *variable) | Constant[pattern, ..., *variable] find_pattern: [*variable, pattern, ..., *variable] | Constant(*variable, pattern, ..., *variable) | Constant[*variable, pattern, ..., *variable] hash_pattern: {key: pattern, key:, ..., **variable} | Constant(key: pattern, key:, ..., **variable) | Constant[key: pattern, key:, ..., **variable]
附錄 B. 一些未定義的行為範例¶ ↑
為了在未來進行最佳化,規範中包含一些未定義的行為。
在未比對的模式中使用變數
case [0, 1] in [a, 2] "not matched" in b "matched" in c "not matched" end a #=> undefined c #=> undefined
deconstruct
、deconstruct_keys
方法呼叫次數
$i = 0 ary = [0] def ary.deconstruct $i += 1 self end case ary in [0, 1] "not matched" in [0] "matched" end $i #=> undefined