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

#to_s หรือ #to_str?การแคสต์อย่างชัดแจ้งกับการบังคับประเภทโดยปริยายใน Ruby

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 มาหาเรา