Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ข้อมูลเบื้องต้นเกี่ยวกับการจับคู่รูปแบบใน Ruby

มาเริ่มด้วยการพูดคุยสั้นๆ เกี่ยวกับการจับคู่รูปแบบใน Ruby กันว่ามันทำงานอย่างไร และจะช่วยปรับปรุงความสามารถในการอ่านโค้ดได้อย่างไร

หากคุณเป็นเหมือนฉันเมื่อสองสามปีก่อน คุณอาจสับสนกับการจับคู่รูปแบบใน Regex แม้แต่การค้นหา 'การจับคู่รูปแบบ' โดย Google อย่างรวดเร็วโดยไม่มีบริบทอื่นทำให้คุณมีเนื้อหาที่ใกล้เคียงกับคำจำกัดความนั้นมาก

อย่างเป็นทางการ การจับคู่รูปแบบคือกระบวนการตรวจสอบข้อมูลใดๆ (ไม่ว่าจะเป็นลำดับของอักขระ ชุดของโทเค็น ทูเพิล หรืออย่างอื่น) เทียบกับข้อมูลอื่นๆ

ในแง่ของการเขียนโปรแกรม ขึ้นอยู่กับความสามารถของภาษา นี่อาจหมายถึงสิ่งต่อไปนี้:

  1. จับคู่กับประเภทข้อมูลที่คาดไว้
  2. จับคู่กับโครงสร้างแฮชที่คาดไว้ (เช่น มีคีย์เฉพาะ)
  3. จับคู่กับความยาวอาร์เรย์ที่คาดไว้
  4. กำหนดการจับคู่ (หรือบางส่วน) ให้กับตัวแปรบางตัว

การโจมตีครั้งแรกของฉันในการจับคู่รูปแบบคือผ่าน 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

ดังที่เราได้เห็นในตัวอย่างข้างต้นบางส่วน การจับคู่รูปแบบมีประโยชน์มากในการกำหนดรูปแบบบางส่วนให้กับตัวแปรตามอำเภอใจ ซึ่งเรียกว่าการเชื่อมโยงตัวแปร และมีหลายวิธีที่เราสามารถผูกกับตัวแปรได้:

  1. ด้วยการจับคู่ประเภทที่รัดกุมเช่น in [Integer => a] หรือ in {a: Integer => a}
  2. ไม่มีข้อกำหนดประเภทเช่น in [a, 1, 2] หรือ in {a: a} .
  3. ไม่มีชื่อตัวแปร ซึ่งค่าเริ่มต้นจะใช้ชื่อคีย์ เช่น in {a:} จะกำหนดตัวแปรชื่อ a ด้วยค่าที่คีย์ a .
  4. ผูกส่วนที่เหลือเช่น 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 ของเราและไม่พลาดแม้แต่โพสต์เดียว!