มาเริ่มด้วยการพูดคุยสั้นๆ เกี่ยวกับการจับคู่รูปแบบใน Ruby กันว่ามันทำงานอย่างไร และจะช่วยปรับปรุงความสามารถในการอ่านโค้ดได้อย่างไร
หากคุณเป็นเหมือนฉันเมื่อสองสามปีก่อน คุณอาจสับสนกับการจับคู่รูปแบบใน Regex แม้แต่การค้นหา 'การจับคู่รูปแบบ' โดย Google อย่างรวดเร็วโดยไม่มีบริบทอื่นทำให้คุณมีเนื้อหาที่ใกล้เคียงกับคำจำกัดความนั้นมาก
อย่างเป็นทางการ การจับคู่รูปแบบคือกระบวนการตรวจสอบข้อมูลใดๆ (ไม่ว่าจะเป็นลำดับของอักขระ ชุดของโทเค็น ทูเพิล หรืออย่างอื่น) เทียบกับข้อมูลอื่นๆ
ในแง่ของการเขียนโปรแกรม ขึ้นอยู่กับความสามารถของภาษา นี่อาจหมายถึงสิ่งต่อไปนี้:
- จับคู่กับประเภทข้อมูลที่คาดไว้
- จับคู่กับโครงสร้างแฮชที่คาดไว้ (เช่น มีคีย์เฉพาะ)
- จับคู่กับความยาวอาร์เรย์ที่คาดไว้
- กำหนดการจับคู่ (หรือบางส่วน) ให้กับตัวแปรบางตัว
การโจมตีครั้งแรกของฉันในการจับคู่รูปแบบคือผ่าน Elixir Elixir รองรับการจับคู่รูปแบบชั้นหนึ่ง มากจน =
โอเปอเรเตอร์คือ match
โอเปอเรเตอร์ มากกว่าการมอบหมายง่ายๆ
ซึ่งหมายความว่าใน Elixir รหัสต่อไปนี้เป็นรหัสที่ถูกต้อง:
iex> x = 1
iex> 1 = x
ด้วยเหตุนี้ เรามาดูการรองรับการจับคู่รูปแบบใหม่สำหรับ Ruby 2.7+ และวิธีที่เราจะใช้เพื่อทำให้โค้ดของเราอ่านง่ายขึ้นได้ตั้งแต่วันนี้
การจับคู่รูปแบบทับทิมกับ case
/in
Ruby รองรับการจับคู่รูปแบบด้วย case
. พิเศษ /in
การแสดงออก. ไวยากรณ์คือ:
case <expression>
in <pattern1>
# ...
in <pattern2>
# ...
else
# ...
end
นี่อย่าไปสับสนกับ case
/when
การแสดงออก. when
และ in
ไม่สามารถผสมสาขาใน case
เดียวได้ .
หากคุณไม่ได้ระบุ else
นิพจน์ การจับคู่ที่ล้มเหลวใด ๆ จะเพิ่ม NoMatchingPatternError
.
รูปแบบการจับคู่อาร์เรย์ใน Ruby
การจับคู่รูปแบบสามารถใช้เพื่อจับคู่อาร์เรย์กับโครงสร้างที่กำหนดไว้ล่วงหน้ากับประเภทข้อมูล ความยาว หรือค่า
ตัวอย่างเช่น รายการต่อไปนี้ทั้งหมดตรงกัน (โปรดทราบว่าเฉพาะ in
. แรกเท่านั้น จะถูกประเมินเป็น case
หยุดดูแลการแข่งขันนัดแรก):
case [1, 2, "Three"]
in [Integer, Integer, String]
"matches"
in [1, 2, "Three"]
"matches"
in [Integer, *]
"matches" # because * is a spread operator that matches anything
in [a, *]
"matches" # and the value of the variable a is now 1
end
ประโยคจับคู่รูปแบบประเภทนี้มีประโยชน์มากเมื่อคุณต้องการสร้างสัญญาณหลายตัวจากการเรียกใช้เมธอด
ในโลกของ Elixir มักใช้เมื่อดำเนินการที่อาจมีทั้ง :ok
ผลลัพธ์และ :error
ผลลัพธ์ เช่น แทรกลงในฐานข้อมูล
ต่อไปนี้คือวิธีที่เราใช้เพื่อให้อ่านง่ายขึ้น:
def create
case save(model_params)
in [:ok, model]
render :json => model
in [:error, errors]
render :json => errors
end
end
# Somewhere in your code, e.g. inside a global helper or your model base class (with a different name).
def save(attrs)
model = Model.new(attrs)
model.save ? [:ok, model] : [:error, model.errors]
end
รูปแบบการจับคู่วัตถุในทับทิม
คุณยังสามารถจับคู่วัตถุใน Ruby เพื่อบังคับใช้โครงสร้างเฉพาะ:
case {a: 1, b: 2}
in {a: Integer}
"matches" # By default, all object matches are partial
in {a: Integer, **}
"matches" # and is same as {a: Integer}
in {a: a}
"matches" # and the value of variable a is now 1
in {a: Integer => a}
"matches" # and the value of variable a is now 1
in {a: 1, b: b}
"matches" # and the value of variable b is now 2
in {a: Integer, **nil}
"does not match" # This will match only if the object has a and no other keys
end
วิธีนี้ใช้ได้ผลดีเมื่อกำหนดกฎเกณฑ์ที่เข้มงวดสำหรับการจับคู่กับพารามิเตอร์ใดๆ
ตัวอย่างเช่น หากคุณกำลังเขียนคำทักทายแบบแฟนซี อาจมีโครงสร้าง (มีความเห็นอย่างแรงกล้า) ดังต่อไปนี้:
def greet(hash = {})
case hash
in {greeting: greeting, first_name: first_name, last_name: last_name}
greet(greeting: greeting, name: "#{first_name} #{last_name}")
in {greeting: greeting, name: name}
puts "#{greeting}, #{name}"
in {name: name}
greet(greeting: "Hello", name: name)
in {greeting: greeting}
greet(greeting: greeting, name: "Anonymous")
else
greet(greeting: "Hello", name: "Anonymous")
end
end
greet # Hello, Anonymous
greet(name: "John") # Hello, John
greet(first_name: "John", last_name: "Doe") # Hello, John Doe
greet(greeting: "Bonjour", first_name: "John", last_name: "Doe") # Bonjour, John Doe
greet(greeting: "Bonjour") # Bonjour, Anonymous
การผูกและการตรึงตัวแปรใน Ruby
ดังที่เราได้เห็นในตัวอย่างข้างต้นบางส่วน การจับคู่รูปแบบมีประโยชน์มากในการกำหนดรูปแบบบางส่วนให้กับตัวแปรตามอำเภอใจ ซึ่งเรียกว่าการเชื่อมโยงตัวแปร และมีหลายวิธีที่เราสามารถผูกกับตัวแปรได้:
- ด้วยการจับคู่ประเภทที่รัดกุมเช่น
in [Integer => a]
หรือin {a: Integer => a}
- ไม่มีข้อกำหนดประเภทเช่น
in [a, 1, 2]
หรือin {a: a}
. - ไม่มีชื่อตัวแปร ซึ่งค่าเริ่มต้นจะใช้ชื่อคีย์ เช่น
in {a:}
จะกำหนดตัวแปรชื่อa
ด้วยค่าที่คีย์a
. - ผูกส่วนที่เหลือเช่น
in [Integer, *rest]
หรือin {a: Integer, **rest}
.
แล้วเราจะจับคู่ได้อย่างไรเมื่อเราต้องการใช้ตัวแปรที่มีอยู่เป็นรูปแบบย่อย? นี่คือเวลาที่เราสามารถใช้ตัวแปร การตรึง ด้วย ^
(พิน) โอเปอเรเตอร์:
a = 1
case {a: 1, b: 2}
in {a: ^a}
"matches"
end
คุณยังสามารถใช้สิ่งนี้เมื่อกำหนดตัวแปรในรูปแบบนั้นเองได้ ทำให้คุณสามารถเขียนรูปแบบที่ทรงพลังเช่นนี้:
case order
in {billing_address: {city:}, shipping_address: {city: ^city}}
puts "both billing and shipping are to the same city"
else
raise "both billing and shipping must be to the same city"
end
สิ่งสำคัญอย่างหนึ่งที่ต้องพูดถึงในการผูกตัวแปรคือแม้ว่ารูปแบบจะไม่ตรงกันทั้งหมด แต่ตัวแปรก็ยังถูกผูกไว้ ซึ่งบางครั้งอาจมีประโยชน์
แต่ในกรณีส่วนใหญ่ นี่อาจเป็นสาเหตุของข้อบกพร่องเล็กๆ น้อยๆ ได้เช่นกัน ดังนั้นตรวจสอบให้แน่ใจว่าคุณไม่ได้พึ่งพาค่าตัวแปรเงาที่ใช้ในการแข่งขัน ตัวอย่างเช่น ต่อไปนี้ คุณคาดหวังให้เมือง เป็น "อัมสเตอร์ดัม" แต่กลับเป็น "เบอร์ลิน" แทน:
city = "Amsterdam"
order = {billing_address: {city: "Berlin"}, shipping_address: {city: "Zurich"}}
case order
in {billing_address: {city:}, shipping_address: {city: ^city}}
puts "both billing and shipping are to the same city"
else
puts "both billing and shipping must be to the same city"
end
puts city # Berlin instead of Amsterdam
จับคู่คลาสที่กำหนดเองของ Ruby
คุณสามารถใช้วิธีการพิเศษบางอย่างเพื่อให้ทราบถึงการจับคู่รูปแบบคลาสที่กำหนดเองใน Ruby
ตัวอย่างเช่น หากต้องการจับคู่รูปแบบกับผู้ใช้กับ first_name
และ last_name
เราสามารถกำหนด deconstruct_keys
ในชั้นเรียน:
class User
def deconstruct_keys(keys)
{first_name: first_name, last_name: last_name}
end
end
case user
in {first_name: "John"}
puts "Hey, John"
end
keys
อาร์กิวเมนต์ถึง deconstruct_keys
มีคีย์ที่ได้รับการร้องขอในรูปแบบนี้เป็นวิธีที่ผู้รับจะจัดหาเฉพาะคีย์ที่จำเป็นหากการคำนวณทั้งหมดมีราคาแพง
เช่นเดียวกับ deconstruct_keys
เราสามารถจัดเตรียมการใช้งาน deconstruct
เพื่อให้วัตถุสามารถจับคู่รูปแบบเป็นอาร์เรย์ได้ ตัวอย่างเช่น สมมติว่าเรามี Location
คลาสที่มีละติจูดและลองจิจูด นอกจากการใช้ deconstruct_keys
เพื่อให้คีย์ละติจูดและลองจิจูด เราสามารถแสดงอาร์เรย์ในรูปแบบของ [latitude, longitude]
เช่นกัน:
class Location
def deconstruct
[latitude, longitude]
end
end
case location
in [Float => latitude, Float => longitude]
puts "#{latitude}, #{longitude}"
end
การใช้การ์ดสำหรับรูปแบบที่ซับซ้อน
หากเรามีรูปแบบที่ซับซ้อนซึ่งไม่สามารถแสดงด้วยตัวดำเนินการจับคู่รูปแบบปกติได้ เราก็สามารถใช้ if
(หรือ unless
) คำสั่งให้ผู้พิทักษ์สำหรับการแข่งขัน:
case [1, 2]
in [a, b] if b == a * 2
"matches"
else
"no match"
end
การจับคู่รูปแบบกับ =>
/in
ไม่มี case
หากคุณใช้ Ruby 3+ คุณจะสามารถเข้าถึงเวทมนตร์การจับคู่รูปแบบได้มากขึ้น เริ่มตั้งแต่ Ruby 3 การจับคู่รูปแบบสามารถทำได้ในบรรทัดเดียวโดยไม่ต้องมีคำสั่ง case:
[1, 2, "Three"] => [Integer => one, two, String => three]
puts one # 1
puts two # 2
puts three # Three
# Same as above
[1, 2, "Three"] in [Integer => one, two, String => three]
เนื่องจากไวยากรณ์ข้างต้นไม่มี else
ข้อ จะมีประโยชน์มากที่สุดเมื่อทราบโครงสร้างข้อมูลล่วงหน้า
ตัวอย่างเช่น รูปแบบนี้สามารถเข้ากันได้ดีกับตัวควบคุมฐานที่อนุญาตเฉพาะผู้ใช้ที่เป็นผู้ดูแลระบบ:
class AdminController < AuthenticatedController
before_action :verify_admin
private
def verify_admin
Current.user => {role: :admin}
rescue NoMatchingPatternError
raise NotAllowedError
end
end
การจับคู่รูปแบบใน Ruby:ดู Space นี้
ในตอนแรก การจับคู่รูปแบบอาจรู้สึกแปลกๆ เล็กน้อยที่จะเข้าใจ สำหรับบางคน อาจรู้สึกเหมือนมีการสร้างวัตถุ/อาร์เรย์ที่ได้รับการยกย่อง
แต่ถ้าความนิยมของ Elixir เป็นสิ่งบ่งชี้ การจับคู่รูปแบบเป็นเครื่องมือที่ยอดเยี่ยมที่จะมีในคลังแสงของคุณ เมื่อมีประสบการณ์โดยตรงในการใช้งาน Elixir ฉันยืนยันได้เลยว่าการใช้ชีวิตโดยปราศจากความคุ้นเคยนั้นเป็นเรื่องยาก
หากคุณใช้ Ruby 2.7 ให้จับคู่รูปแบบ (ด้วย case
/in
) ยังอยู่ในช่วงทดลอง พร้อมทับทิม 3 case
/in
ได้ย้ายไปที่เสถียรในขณะที่นิพจน์การจับคู่รูปแบบบรรทัดเดียวที่เพิ่งเปิดตัวใหม่เป็นแบบทดลอง สามารถปิดคำเตือนได้ด้วย Warning[:experimental] = false
ในโค้ดหรือ -W:no-experimental
ปุ่มบรรทัดคำสั่ง
แม้ว่าการจับคู่รูปแบบใน Ruby จะยังอยู่ในช่วงเริ่มต้น แต่ฉันหวังว่าคุณจะพบว่าบทแนะนำนี้มีประโยชน์และรู้สึกตื่นเต้นเช่นเดียวกับที่ฉันตื่นเต้นเกี่ยวกับการพัฒนาในอนาคตที่จะมาถึง!
ป.ล. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่ออกจากสื่อ สมัครรับจดหมายข่าว Ruby Magic ของเราและไม่พลาดแม้แต่โพสต์เดียว!