Type coercion คือการเปลี่ยนประเภทของวัตถุไปเป็นอีกประเภทหนึ่งพร้อมกับค่าของวัตถุนั้น ตัวอย่างเช่น การเปลี่ยนจำนวนเต็มเป็นสตริงด้วย #to_s
หรือลอยเป็นจำนวนเต็มด้วย #to_i
. #to_str
. ที่อาจจะไม่ค่อยมีใครรู้จัก และ #to_int
วิธีการที่ใช้วัตถุบางอย่างทำเช่นเดียวกันในแวบแรก แต่มีข้อแตกต่างบางประการ
ใน AppSignal academy ฉบับนี้ เราจะเจาะลึกถึงประเภทการแคสต์อย่างชัดเจนและบังคับโดยปริยายใน Ruby ในขณะที่กล่าวถึงนักแสดงที่พิมพ์ดีดโดยสังเขป เราจะอธิบายข้อแตกต่างระหว่างทั้งสองวิธีและอภิปรายถึงวิธีการใช้
อันดับแรก มาดูว่าเรามักจะบังคับให้ค่าต่างๆ ใน Ruby ต่างกันอย่างไรโดยใช้ตัวช่วยแคสต์ที่ชัดเจน
ตัวช่วยในการแคสต์อย่างโจ่งแจ้ง
ตัวช่วยแคสต์ที่พบบ่อยที่สุดคือ #to_s
, #to_i
, #to_a
และ #to_h
. นี่เป็นวิธีการแคสต์ที่ชัดเจน ช่วยให้เราแปลงค่าจากประเภทหนึ่งเป็นอีกประเภทหนึ่งได้อย่างง่ายดาย
ผู้ช่วยที่ชัดเจนมาพร้อมกับสัญญาที่ชัดเจน เมื่อใดก็ตามที่ #to_s
ถูกเรียกบนวัตถุ มันจะ เสมอ คืนค่าสตริง แม้ว่าอ็อบเจ็กต์จะแปลงเป็นสตริงไม่ได้จริงๆ มันเหมือนกับการคัดเลือก Michael Keaton เป็นแบทแมน คุณจะได้แบทแมนแม้ว่านักแสดงตลกจะไม่เหมาะกับบทนี้โดยเฉพาะ
Ruby เสนอวิธีการช่วยเหลือเหล่านี้ในเกือบทุกวัตถุพื้นฐานในไลบรารีมาตรฐาน Ruby
:foo.to_s # => "foo"
10.0.to_i # => 10
"10".to_i # => 10
วิธีการเหล่านี้ โดยเฉพาะ #to_s
ถูกนำไปใช้กับประเภทพื้นฐานส่วนใหญ่ใน Ruby แม้ว่าการแคสต์จะส่งคืนค่าเกือบทุกครั้ง แต่ผลลัพธ์อาจไม่ใช่สิ่งที่เราคาดหวัง
"foo10".to_i # => 0
[1, 2, 3].to_s # => "[1, 2, 3]"
{ :foo => :bar }.to_s # => "{:foo=>:bar}"
{ :foo => :bar }.to_a # => [[:foo, :bar]]
Object.to_s # => "Object"
Object.new.to_s # => "#<Object:0x00007f8e6d053a90>"
กำลังเรียก #to_s
, #to_i
, #to_a
และ #to_h
ตัวช่วยบังคับค่าใด ๆ ให้กับประเภทที่เลือก ส่งคืนการแสดงประเภทที่ถูกบังคับโดยไม่คำนึงถึงสิ่งที่เกิดขึ้นกับค่า
วิธีการบังคับโดยปริยาย
การเรียกใช้วิธีการแคสต์ประเภทกับค่าที่ไม่เหมือนกับประเภทที่เรากำลังแคสต์อาจทำให้เกิดข้อผิดพลาดหรือข้อมูลสูญหายได้ Ruby ยังเสนอวิธีการบังคับโดยปริยายซึ่งจะคืนค่าเฉพาะเมื่อวัตถุทำตัวเหมือนประเภท วิธีนี้ทำให้เรามั่นใจได้ว่าค่าจะทำหน้าที่เหมือนแบบที่เราต้องการ วิธีการบังคับโดยปริยายเหล่านี้คือ #to_str
, #to_int
, #to_ary
และ #to_hash
.
การบีบบังคับโดยปริยายก็เหมือนกับการคัดเลือกลีโอนาร์ด นิมอยเป็นบทบาทใดๆ ก็ตาม ยกเว้นสป็อค พวกเขาจะใช้ได้ถ้าตัวละครอยู่ใกล้กับสป็อค แต่ล้มเหลวถ้าไม่ใช่ #to_str
ผู้ช่วย พยายาม เพื่อแปลงเป็นสตริง แต่จะยก NoMethodError
หากวัตถุไม่ได้ใช้วิธีการและไม่สามารถบังคับโดยปริยายได้
10.to_int # => 10
10.0.to_int # => 10
require "bigdecimal"
BigDecimal.new("10.0000123").to_int # => 10
# Unsuccessful coercions
"10".to_int # => NoMethodError
"foo10".to_int # => NoMethodError
[1, 2, 3].to_str # => NoMethodError
{ :foo => :bar }.to_str # => NoMethodError
{ :foo => :bar }.to_ary # => NoMethodError
Object.to_str # => NoMethodError
Object.new.to_str # => NoMethodError
เราจะเห็นได้ว่าตอนนี้ Ruby เข้มงวดขึ้นเล็กน้อยในสิ่งที่ทำและไม่บังคับกับประเภทที่ร้องขอ หากไม่สามารถบังคับได้ #to_*
ไม่ได้ใช้งานเมธอดบนวัตถุและเรียกมันว่า NoMethodError
.
เมื่อใช้บังคับโดยปริยาย เช่น #to_str
เราขอให้ฟังก์ชันส่งคืนอ็อบเจ็กต์ String เฉพาะในกรณีที่ประเภทดั้งเดิมยังทำหน้าที่เหมือน String ด้วยเหตุนี้ #to_str
ถูกนำไปใช้กับ String ใน Ruby Standard Library เท่านั้น
ทับทิมใช้การบังคับโดยปริยายอย่างไร
นอกจากจะแม่นยำมากขึ้นในสิ่งที่เราขอในระหว่างการบีบบังคับแล้ว การบีบบังคับโดยปริยายมีประโยชน์อะไรอีกบ้าง ปรากฎว่า Ruby ใช้การบีบบังคับโดยปริยายในสถานการณ์สมมติ ตัวอย่างเช่น เมื่อรวมอ็อบเจ็กต์กับ +
.
name = "world!"
"Hello " + name # => "Hello world!"
# Without #to_str
class Name
def initialize(name)
@name = name
end
end
"Hello " + Name.new("world!") # => TypeError: no implicit conversion of Name into String
ที่นี่เราเห็น Ruby เพิ่ม TypeError
เนื่องจากไม่สามารถแปลงโดยนัยจาก Name
พิมพ์เป็น String
.
หากเราใช้ #to_str
ในชั้นเรียน Ruby รู้วิธีบังคับ Name
ชนิด
# With #to_str
class Name
def to_str
@name
end
end
"Hello " + Name.new("world!") # => "Hello world!"
ใช้งานได้เหมือนกันสำหรับ Arrays และ #to_ary
.
class Options
def initialize
@internal = []
end
def <<(value)
@internal << value
end
end
options = Options.new
options << :foo
[:some_prefix] + options # => TypeError: no implicit conversion of Options into Array
class Options
def to_ary
@internal
end
end
[:some_prefix] + options # => [:some_prefix, :foo]
แต่ #to_ary
ใช้ในสถานการณ์ต่างๆ มากขึ้น เราสามารถใช้มันเพื่อทำลาย Array ให้เป็นตัวแปรแยกกันได้
options = Options.new
options << :first
options << :second
options << :third
first, second, third = options
first # => :first
second # => :second
third # => :third
นอกจากนี้ยังทำการแปลงวัตถุเป็นพารามิเตอร์ของบล็อกด้วย
[options].each do |(first, second)|
first # => :first
second # => :second
end
มีสถานการณ์อื่นๆ ที่ใช้วิธีการบังคับโดยนัย เช่น #to_hash
ด้วย **
. สิ่งนี้บังคับค่าเป็นแฮชด้วย #to_hash
ก่อนจะส่งต่อไปยัง parse_options
วิธีการ
class Options
def to_hash
# Create a hash from the Options Array
Hash[*@internal]
end
end
def parse_options(opts)
opts
end
options = Options.new
options << :key
options << :value
parse_options(**options) # => {:key=>:value}
การบังคับใช้ประเภท
Ruby ยังเสนอวิธีการบังคับที่ยืดหยุ่นกว่าเมื่อประเภทนั้นเป็นประเภทที่ไม่รู้จัก และเราต้องการให้แน่ใจว่าได้ประเภทที่ถูกต้อง มีหนึ่งประเภทสำหรับพื้นฐานทุกประเภท (String(...)
, Integer(...)
, Float(...)
, Array(...)
, Hash(...)
เป็นต้น)
String(self) # => "main"
String(self.class) # => "Object"
String(123456) # => "123456"
String(nil) # => ""
Integer(123.999) # => 123
Integer("0x1b") # => 27
Integer(Time.new) # => 1204973019
Integer(nil) # => TypeError: can't convert nil into Integer
String(...)
วิธีแรกพยายามเรียก #to_str
เกี่ยวกับค่า และเมื่อล้มเหลว จะเรียก #to_s
กระบวนการ. ไม่ใช่ทุกอ็อบเจ็กต์ที่กำหนด #to_str
วิธีการจึงตรวจสอบด้วยทั้งการบังคับโดยปริยาย (#to_str
) และชัดเจน (#to_s
) วิธีการแคสต์จะเพิ่มโอกาสที่การแปลงสตริงจะทำงานและคุณจะได้รับมูลค่าที่ต้องการ ในการเรียกการบังคับโดยปริยายในครั้งแรก เรามักจะได้ผลลัพธ์ที่มีค่าเท่ากันแต่เป็นประเภทบังคับ ไม่ใช่ "#<Object:0x00007f8e6d053a90>"
.
class MyString
def initialize(value)
@value = value
end
def to_str
@value
end
end
s = MyString.new("hello world")
s.to_s # => "#<MyString:0x...>"
s.to_str # => "hello world"
String(s) # => "hello world"
คุณควรใช้วิธีการคัดเลือกโดยปริยายสำหรับวัตถุที่ทำหน้าที่เหมือนถูกบังคับเช่น #to_str
สำหรับคลาสสตริงของคุณเอง
นอกเหนือจากการพยายามบังคับโดยปริยายในครั้งแรกแล้ว String(...)
ผู้ช่วยยังตรวจสอบประเภทที่ส่งคืน #to_str
เป็นเพียงวิธีการที่สามารถคืนค่าประเภทใดก็ได้ แม้กระทั่งไม่ใช่สตริง เพื่อให้แน่ใจว่าเราได้รับค่าของประเภทที่ร้องขอ String(...)
ยก TypeError
หากประเภทไม่ตรงกัน
class MyString
def to_str
nil
end
end
s = MyString.new("hello world")
s.to_s # => "#<MyString:0x...>"
s.to_str # => nil
String(s) # => "#<MyString:0x...>"
ในที่นี้ เราจะเห็นว่า Ruby ละเว้นผลลัพธ์ของ #to_str
เพราะมันกลับ nil
ซึ่งไม่ใช่ประเภทสตริง แต่จะกลับไปที่ #to_s
ผลลัพธ์
ถ้า #to_s
ยังส่งคืน nil
และไม่ใช่ประเภทที่ถูกต้อง String(...)
จะเพิ่ม TypeError
.
class MyString
def to_str
nil
end
def to_s
nil
end
end
s = MyString.new("hello world")
s.to_s # => nil
s.to_str # => nil
String(s) # => TypeError: can't convert MyString to String (MyString#to_s gives NilClass)
แม้ว่าการบังคับใช้การบีบบังคับแบบต่างๆ อาจเชื่อถือได้มากกว่า แต่โปรดทราบว่าเมธอดตัวช่วยการแคสต์ (String(...)
, Integer(...)
ฯลฯ) มักจะช้ากว่าเล็กน้อยเนื่องจากจำเป็นต้องทำการตรวจสอบเพิ่มเติมกับค่าที่กำหนด
ในบทสรุป
เมื่อคุณต้องการให้แน่ใจว่าคุณกำลังจัดการกับประเภทข้อมูลที่ถูกต้องสำหรับออบเจกต์ การบีบบังคับแบบพิมพ์เป็นกระบวนการที่มีประโยชน์ ในโพสต์นี้ เราได้รีเฟรชความรู้เกี่ยวกับตัวช่วยแคสต์ที่ชัดเจน เช่น #to_s
, #to_i
, #to_a
และ #to_h
. นอกจากนี้เรายังพิจารณากรณีที่ผู้ช่วยเหลือโดยนัยเช่น #to_str
, #to_int
, #to_ary
และ #to_hash
มีประโยชน์และวิธีที่ Ruby ใช้เอง
เราหวังว่าคุณจะพบว่าภาพรวมการบีบบังคับประเภทนี้มีประโยชน์และวิธีที่คุณพบว่านักแสดงนำการเปรียบเทียบแบบพิมพ์ดีด และเช่นเคย โปรดแจ้งให้เราทราบหากมีหัวข้อที่คุณต้องการให้เราพูดถึง หากคุณมีคำถามหรือความคิดเห็นใด ๆ อย่าลังเลที่จะส่ง @AppSignal มาหาเรา