RBS เป็นชื่อของภาษารูปแบบไวยากรณ์ใหม่สำหรับ Ruby RBS ให้คุณเพิ่มคำอธิบายประกอบประเภทให้กับรหัส Ruby ของคุณในไฟล์ที่มีนามสกุลใหม่ชื่อ .rbs . มีลักษณะดังนี้:
class MyClass
def my_method : (my_param: String) -> String
end
การให้คำอธิบายประกอบประเภทกับ RBS ทำให้คุณได้รับประโยชน์ต่างๆ เช่น:
- วิธีที่ชัดเจนและรัดกุมในการกำหนดโครงสร้างของโค้ดเบสของคุณ
- วิธีที่ปลอดภัยกว่าในการเพิ่มประเภทให้กับโค้ดเดิมของคุณผ่านไฟล์ แทนที่จะเปลี่ยนคลาสโดยตรง
- ศักยภาพในการรวมเข้ากับตัวตรวจสอบประเภทสแตติกและไดนามิกในระดับสากล
- คุณลักษณะใหม่เพื่อจัดการกับวิธีการโอเวอร์โหลด การพิมพ์แบบเป็ด อินเทอร์เฟซแบบไดนามิก และอื่นๆ
แต่เดี๋ยวก่อน! ไม่มีตัวตรวจสอบประเภทคงที่เช่น Sorbet และ Steep หรือไม่
ใช่ และพวกเขายอดเยี่ยมมาก! อย่างไรก็ตาม หลังจากพูดคุยกันสี่ปีและเครื่องมือตรวจสอบประเภทที่สร้างโดยชุมชนจำนวนหนึ่ง ทีมตัวแทน Ruby คิดว่าถึงเวลากำหนดมาตรฐานสำหรับเครื่องมือตรวจสอบประเภทอาคารแล้ว
RBS เป็นภาษาทางการและกำลังจะมีชีวิตพร้อมกับ Ruby 3
นอกจากนี้ เนื่องจากลักษณะการพิมพ์แบบไดนามิกของ Ruby เช่นเดียวกับรูปแบบทั่วไป เช่น Duck Typing และ method overloading เราจึงได้กำหนดมาตรการป้องกันบางประการเพื่อให้แน่ใจว่าได้แนวทางที่ดีที่สุดเท่าที่จะเป็นไปได้ ซึ่งเราจะเห็นในรายละเอียดเพิ่มเติมในไม่ช้านี้
ติดตั้ง Ruby 3
เพื่อทำตามตัวอย่างที่แสดงที่นี่ เราต้องติดตั้ง Ruby 3
คุณสามารถทำได้โดยทำตามคำแนะนำอย่างเป็นทางการหรือผ่าน ruby-build ในกรณีที่คุณต้องจัดการ Ruby หลายเวอร์ชัน
หรือคุณสามารถติดตั้ง rbs
อัญมณีโดยตรงในโครงการปัจจุบันของคุณ:
gem install rbs
การพิมพ์แบบคงที่เทียบกับแบบไดนามิก
ก่อนจะไปต่อ เรามาชี้แจงแนวคิดนี้กันก่อน ภาษาที่พิมพ์แบบไดนามิกเปรียบเทียบกับภาษาคงที่ได้อย่างไร
ในภาษาที่พิมพ์แบบไดนามิก เช่น Ruby และ JavaScript ไม่มีประเภทข้อมูลที่กำหนดไว้ล่วงหน้าสำหรับล่ามเพื่อทำความเข้าใจวิธีดำเนินการในกรณีที่มีการดำเนินการที่ต้องห้ามระหว่างรันไทม์
ซึ่งตรงกันข้ามกับสิ่งที่คาดหวังจากการพิมพ์แบบคงที่ ภาษาที่พิมพ์แบบสแตติก เช่น Java และ C จะตรวจสอบประเภทระหว่างเวลาที่คอมไพล์
ใช้ข้อมูลโค้ด Java ต่อไปนี้เป็นข้อมูลอ้างอิง:
int number = 0;
number = "Hi, number!";
ไม่สามารถทำได้ในการพิมพ์แบบคงที่ เนื่องจากบรรทัดที่สองจะเกิดข้อผิดพลาด:
error: incompatible types: String cannot be converted to int
ตอนนี้ ให้ยกตัวอย่างเดียวกันใน Ruby:
number = 0;
number = "Hi, number!";
puts number // Successfully prints "Hi, number!"
ใน Ruby ประเภทของตัวแปรจะแตกต่างกันไป ซึ่งหมายความว่าล่ามรู้วิธีสลับจากตัวแปรหนึ่งไปยังอีกแบบไดนามิก
แนวคิดนั้นมักสับสนกับ พิมพ์ดีด vs พิมพ์ไม่เก่ง ภาษา
Ruby ไม่ได้เป็นเพียงไดนามิกเท่านั้น แต่ยังเป็นภาษาที่มีการพิมพ์อย่างมาก ซึ่งหมายความว่าช่วยให้ตัวแปรเปลี่ยนประเภทได้ระหว่างรันไทม์ อย่างไรก็ตาม มันไม่อนุญาตให้คุณทำการผสมแบบบ้าๆ บอๆ
ยกตัวอย่างต่อไป (ใน Ruby) ที่ดัดแปลงมาจากตัวอย่างก่อนหน้า:
number = 2;
sum = "2" + 2;
puts sum
คราวนี้เรากำลังลองผลรวมของตัวเลขสองตัวที่เป็นของประเภทต่างๆ (Integer
และ String
). Ruby จะโยนข้อผิดพลาดต่อไปนี้:
main.rb:2:in `+': no implicit conversion of Integer into String (TypeError)
from main.rb:2:in `<main>'
กล่าวอีกนัยหนึ่ง Ruby บอกว่างานสำหรับการคำนวณที่ซับซ้อนที่เกี่ยวข้องกับ (อาจ) ประเภทต่างๆ นั้นเป็นของคุณทั้งหมด
ในทางกลับกัน JavaScript ซึ่งค่อนข้าง และ พิมพ์แบบไดนามิก อนุญาตให้ใช้รหัสเดียวกันกับผลลัพธ์ที่แตกต่างกัน:
> number = 2
sum = "2" + 2
console.log(sum)
> 22 // it concatenates both values
RBS แตกต่างจาก Sorbet อย่างไร
อันดับแรก จากแนวทางที่แต่ละคนใช้ไปจนถึงการใส่คำอธิบายประกอบโค้ด ในขณะที่ Sorbet ทำงานโดยการเพิ่มคำอธิบายประกอบอย่างชัดแจ้งในโค้ดของคุณ RBS ก็ต้องการการสร้างไฟล์ใหม่ด้วย .rbs นามสกุล.
ข้อได้เปรียบหลักของสิ่งนี้คือเมื่อเรานึกถึงการย้ายฐานรหัสดั้งเดิม เนื่องจากไฟล์ต้นฉบับของคุณจะไม่ได้รับผลกระทบ การนำไฟล์ RBS ไปใช้ในโครงการของคุณจึงปลอดภัยกว่ามาก
ตามที่ผู้สร้างกล่าว เป้าหมายหลักของ RBS คือการ อธิบายโครงสร้าง ของโปรแกรม Ruby ของคุณ โดยเน้นที่การกำหนดลายเซ็นคลาส/เมธอดเท่านั้น
RBS เองไม่สามารถพิมพ์เช็คได้ เป้าหมายของมันถูกจำกัดให้กำหนดโครงสร้างเป็นพื้นฐานสำหรับตัวตรวจสอบประเภท (เช่น Sorbet และ Steep) เพื่อทำงานของพวกเขา
มาดูตัวอย่างการสืบทอด Ruby อย่างง่าย:
class Badger
def initialize(brand)
@brand = brand
end
def brand?
@brand
end
end
class Honey < Badger
def initialize(brand: "Honeybadger", sweet: true)
super(brand)
@sweet = sweet
end
def sweet?
@sweet
end
end
ยอดเยี่ยม! เพียงสองคลาสที่มีคุณสมบัติและประเภทที่อนุมานบางส่วน
ด้านล่างนี้ เราสามารถเห็นการแสดง RBS ที่เป็นไปได้:
class Brand
attr_reader brand : String
def initialize : (brand: String) -> void
end
class Honey < Brand
@sweet : bool
def initialize : (brand: String, ?sweet: bool) -> void
def sweet? : () -> bool
end
ค่อนข้างคล้ายกันใช่ไหม ความแตกต่างที่สำคัญที่นี่คือประเภท initialize
วิธีการของ Honey
คลาส เช่น รับ String
. หนึ่งอัน และ boolean
. หนึ่งตัว พารามิเตอร์และไม่ส่งคืนสิ่งใด
ในขณะเดียวกัน ทีม Sorbet กำลังทำงานอย่างใกล้ชิดในการสร้างเครื่องมือเพื่อให้สามารถทำงานร่วมกันได้ระหว่าง RBI (ส่วนขยายเริ่มต้นของ Sorbet สำหรับคำจำกัดความประเภท) และ RBS
เป้าหมายคือการวางรากฐานสำหรับ Sorbet และตัวตรวจสอบประเภทใดๆ เพื่อทำความเข้าใจวิธีใช้ไฟล์คำจำกัดความประเภท RBS
เครื่องมือนั่งร้าน
หากคุณเริ่มด้วยภาษาและมีโครงการอยู่แล้ว อาจเป็นเรื่องยากที่จะคาดเดาว่าจะเริ่มพิมพ์จากที่ใดและอย่างไร
เมื่อคำนึงถึงสิ่งนี้ ทีม Ruby ได้จัดเตรียมเครื่องมือ CLI ที่เป็นประโยชน์ชื่อ rbs
กับประเภทนั่งร้านสำหรับชั้นเรียนที่มีอยู่
หากต้องการแสดงรายการคำสั่งที่ใช้ได้ เพียงพิมพ์ rbs help
ในคอนโซลและตรวจสอบผลลัพธ์:
คำสั่งที่มีอยู่ในเครื่องมือ CLI
บางทีคำสั่งที่สำคัญที่สุดในรายการคือ prototype
เนื่องจากจะวิเคราะห์ AST ของไฟล์ซอร์สโค้ดที่ให้ไว้เป็นพารามิเตอร์เพื่อสร้างโค้ด RBS "โดยประมาณ"
ประมาณว่าไม่ได้ผล 100% เนื่องจากรหัสเดิมของคุณไม่ได้ถูกพิมพ์เป็นหลัก เนื้อหาที่มีโครงนั่งร้านส่วนใหญ่จะมาในลักษณะเดียวกัน RBS ไม่สามารถเดาบางประเภทได้หากไม่มีการกำหนดที่ชัดเจน เป็นต้น
ลองใช้อีกตัวอย่างหนึ่งเป็นข้อมูลอ้างอิง คราวนี้เกี่ยวข้องกับสามคลาสที่แตกต่างกันในมรดกที่เรียงต่อกัน:
class Animal
def initialize(weight)
@weight = weight
end
def breathe
puts "Inhale/Exhale"
end
end
class Mammal < Animal
def initialize(weight, is_terrestrial)
super(weight)
@is_terrestrial = is_terrestrial
end
def nurse
puts "I'm breastfeeding"
end
end
class Cat < Mammal
def initialize(weight, n_of_lives, is_terrestrial: true)
super(weight, is_terrestrial)
@n_of_lives = n_of_lives
end
def speak
puts "Meow"
end
end
คลาสง่ายๆ ที่มีคุณลักษณะและเมธอด โปรดทราบว่าหนึ่งในนั้นจะมีค่าบูลีนเริ่มต้น ซึ่งเป็นสิ่งสำคัญในการแสดงให้เห็นว่า RBS มีความสามารถอะไรเมื่อคาดเดาด้วยตัวมันเอง
ตอนนี้ ในการนั่งร้านประเภทเหล่านี้ ให้เรียกใช้คำสั่งต่อไปนี้:
rbs prototype rb animal.rb mammal.rb cat.rb
คุณสามารถส่งไฟล์ Ruby ได้มากเท่าที่คุณต้องการ ต่อไปนี้เป็นผลลัพธ์ของการดำเนินการนี้:
class Animal
def initialize: (untyped weight) -> untyped
def breathe: () -> untyped
end
class Mammal < Animal
def initialize: (untyped weight, untyped is_terrestrial) -> untyped
def nurse: () -> untyped
end
class Cat < Mammal
def initialize: (untyped weight, untyped n_of_lives, ?is_terrestrial: bool is_terrestrial) -> untyped
def speak: () -> untyped
end
ตามที่เราคาดการณ์ไว้ RBS ไม่เข้าใจประเภทส่วนใหญ่ที่เราตั้งเป้าไว้เมื่อเราสร้างชั้นเรียน
งานส่วนใหญ่ของคุณคือเปลี่ยน untyped
. ด้วยตนเอง อ้างอิงถึงของจริง การสนทนาบางส่วนมุ่งหวังที่จะหาวิธีที่ดีกว่าในการบรรลุเป้าหมายนี้กำลังเกิดขึ้นในชุมชนในขณะนี้
Metaprogramming
เมื่อพูดถึง metaprogramming rbs
เครื่องมือจะไม่ช่วยอะไรมากเนื่องจากมีลักษณะแบบไดนามิก
ใช้ชั้นเรียนต่อไปนี้เป็นตัวอย่าง:
class Meta
define_method :greeting, -> { puts 'Hi there!' }
end
Meta.new.greeting
ต่อไปนี้จะเป็นผลของการนั่งร้านประเภทนี้:
class Meta
end
พิมพ์เป็ด
Ruby ไม่ได้กังวลมากนักเกี่ยวกับธรรมชาติของวัตถุ (ประเภทของวัตถุ) แต่สนใจในสิ่งที่พวกเขาสามารถทำได้ (สิ่งที่พวกเขาทำ)
การพิมพ์เป็ดเป็นรูปแบบการเขียนโปรแกรมที่มีชื่อเสียงที่ทำงานตามคำขวัญ:
"ถ้าสิ่งของมีพฤติกรรมเหมือนเป็ด (พูด เดิน บิน ฯลฯ) สิ่งนั้นก็คือเป็ด"
กล่าวอีกนัยหนึ่ง Ruby จะปฏิบัติต่อมันเหมือนเป็ดเสมอ แม้ว่าคำจำกัดความและประเภทดั้งเดิมของมันไม่ควรเป็นตัวแทนของเป็ด
อย่างไรก็ตาม การพิมพ์แบบเป็ดอาจซ่อนรายละเอียดของการใช้งานโค้ดที่อาจยุ่งยากและค้นหา/อ่านได้ยาก
RBS แนะนำแนวคิดของ ประเภทอินเทอร์เฟซ ซึ่งเป็นชุดของเมธอดที่ไม่ขึ้นกับคลาสหรือโมดูลที่เป็นรูปธรรม
มาดูตัวอย่างการสืบทอดสัตว์ก่อนหน้านี้ และสมมติว่าเรากำลังเพิ่มระดับลำดับชั้นใหม่สำหรับสัตว์บกซึ่ง Cat
ของเราใช้ จะสืบทอดไปยัง:
class Terrestrial < Animal
def initialize(weight)
super(weight)
end
def run
puts "Running..."
end
end
เพื่อหลีกเลี่ยงไม่ให้วัตถุลูกนอกโลกทำงาน เราสามารถสร้างอินเทอร์เฟซที่ตรวจสอบประเภทเฉพาะสำหรับการกระทำดังกล่าว:
interface _CanRun
# Requires `<<` operator which accepts `Terrestrial` object.
def <<: (Terrestrial) -> void
end
เมื่อทำการแมปโค้ด RBS กับวิธีการรันเฉพาะ นั่นคือลายเซ็น:
def run: (_CanRun) -> void
เมื่อใดก็ตามที่มีคนพยายามส่งสิ่งอื่นที่ไม่ใช่วัตถุภาคพื้นดินไปยังเมธอด ตัวตรวจสอบประเภทจะทำให้แน่ใจว่าได้บันทึกข้อผิดพลาดแล้ว
ประเภทของสหภาพ
เป็นเรื่องปกติในหมู่ Rubyists ที่จะมีนิพจน์ที่มีค่าประเภทต่างๆ
def fly: () -> (Mammal | Bird | Insect)
RBS รองรับประเภทสหภาพโดยเพียงแค่เข้าร่วมผ่าน pipe โอเปอเรเตอร์
วิธีการโอเวอร์โหลด
แนวทางปฏิบัติทั่วไปอีกประการหนึ่ง (ตามจริงในภาษาการเขียนโปรแกรมหลายภาษา) คือการอนุญาตให้มีการโอเวอร์โหลดเมธอด ซึ่งคลาสสามารถมีเมธอดมากกว่าหนึ่งวิธีในชื่อเดียวกัน แต่ลายเซ็นจะต่างกัน (เช่น ประเภทหรือจำนวนของพารามิเตอร์ ลำดับ ฯลฯ )
มาดูตัวอย่างกันที่สัตว์สามารถคืนลูกพี่ลูกน้องวิวัฒนาการที่ใกล้เคียงที่สุดได้:
def evolutionary_cousins: () -> Enumerator[Animal, void] | { (Animal) -> void } -> void
ด้วยวิธีนี้ RBS ช่วยให้เราระบุได้อย่างชัดเจนว่าสัตว์ตัวใดตัวหนึ่งจะมีลูกพี่ลูกน้องวิวัฒนาการเพียงตัวเดียวหรือหลายตัว
TypeProf
ในขณะเดียวกัน ทีม Ruby ก็ได้เริ่มโครงการใหม่ที่เรียกว่า typeprof ซึ่งเป็นตัวแปล Ruby ระดับประเภททดลองที่มีจุดมุ่งหมายเพื่อวิเคราะห์และ (พยายาม) สร้างเนื้อหา RBS
มันทำงานโดยการตีความที่เป็นนามธรรมและยังคงใช้ขั้นตอนแรกสู่การปรับแต่งที่ดีขึ้น ดังนั้นโปรดใช้ความระมัดระวังเมื่อใช้งานเพื่อวัตถุประสงค์ในการผลิต
ในการติดตั้ง เพียงเพิ่มอัญมณีลงในโปรเจ็กต์ของคุณ:
gem install typeprof
โปรดทราบว่าต้องใช้เวอร์ชัน Ruby ที่มากกว่า 2.7
ใช้ Animal
เวอร์ชันต่อไปนี้ คลาส:
class Animal
def initialize(weight)
@weight = weight
end
def die(age)
if age > 50
true
elsif age <= 50
false
elsif age < 0
nil
end
end
end
Animal.new(100).die(65)
ขึ้นอยู่กับสิ่งที่เกิดขึ้นภายในวิธีการ age
และการเรียกใช้วิธีการเดียวกันต่อไป TypeProf สามารถอนุมานประเภทที่จัดการในโค้ดได้อย่างชาญฉลาด
เมื่อคุณเรียกใช้ typeprof animal.rb
คำสั่งที่ควรเป็นผลลัพธ์:
## Classes
class Animal
@weight: Integer
def initialize: (Integer weight) -> Integer
def die: (Integer age) -> bool?
end
เป็นเครื่องมืออันทรงพลังที่มีข้อเสนอมากมายสำหรับโครงการที่มีโค้ดจำนวนมากอยู่แล้ว
การรวมโค้ด VS
ปัจจุบันยังไม่มีปลั๊กอิน VS Code จำนวนมากสำหรับจัดการกับ RBS สำหรับการจัดรูปแบบ การตรวจสอบโครงสร้าง ฯลฯ โดยเฉพาะอย่างยิ่งเนื่องจากยังค่อนข้างใหม่
อย่างไรก็ตาม หากคุณค้นหา "RBS" ในร้านค้า คุณอาจพบปลั๊กอินหนึ่งชื่อ ruby-signature ซึ่งจะช่วยในการเน้นไวยากรณ์ดังที่แสดงด้านล่าง:
การเน้นไวยากรณ์ RBS ใน VS Code
บทสรุป
RBS มีความสดใหม่และเป็นก้าวสำคัญสู่ฐานรหัส Ruby ที่ปลอดภัยยิ่งขึ้น
โดยปกติ เครื่องมือและโปรเจ็กต์โอเพ่นซอร์สใหม่ๆ จะต้องสำรองข้อมูลไว้ เช่น RBS Rails สำหรับสร้างไฟล์ RBS สำหรับแอปพลิเคชัน Ruby on Rails
อนาคตมีสิ่งที่น่าทึ่งสำหรับชุมชน Ruby ด้วยแอปพลิเคชันที่ปลอดภัยและปราศจากข้อผิดพลาดมากขึ้น อดใจรอไม่ไหวแล้ว!