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

การผูกและขอบเขตคำศัพท์ใน Ruby

สวัสดีปีใหม่ และยินดีต้อนรับกลับสู่ Ruby Magic! ในตอนฤดูหนาวนี้ เราจะเจาะลึกถึงขอบเขตและขอบเขต ดังนั้นสวมสกีของคุณและตามเราไปลึกเข้าไปในป่า

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

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

เราได้สำรวจฟังก์ชันระดับเฟิร์สคลาสของ Ruby แล้ว แต่เราข้าม สภาพแวดล้อม ไปอย่างสะดวก . ในตอนนี้ เราจะมาดูว่าสภาพแวดล้อมนั้นทำงานอย่างไรสำหรับการปิด คลาส และอินสแตนซ์ของคลาส โดยพิจารณาว่า Ruby จัดการกับ ขอบเขตคำศัพท์ อย่างไร ผ่านการผูกมัด .

ขอบเขตคำศัพท์

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

Ruby ก็เหมือนกับภาษาโปรแกรมสมัยใหม่ส่วนใหญ่ที่ใช้ static scope ซึ่งมักเรียกว่า lexical scope (ตรงข้ามกับขอบเขตไดนามิก) ขอบเขตปัจจุบันขึ้นอยู่กับโครงสร้างของรหัสและกำหนดตัวแปรที่มีอยู่ในส่วนเฉพาะของรหัส ซึ่งหมายความว่าขอบเขตจะเปลี่ยนไปเมื่อโค้ดข้ามไปมาระหว่างเมธอด บล็อก และคลาส เนื่องจากอาจมีตัวแปรในเครื่องต่างกันได้ เป็นต้น

def bar
  foo = 1
  foo
end
 
bar #  => 1

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

foo = 1
 
def bar
  foo
end
 
bar # => NameError (undefined local variable or method `foo' for main:Object)

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

@foo = 1
 
def bar
  @foo
end
 
bar #  => 1

ในขณะที่ตัวแปรโลคัลพร้อมใช้งานในเครื่อง ตัวแปรอินสแตนซ์ ใช้ได้กับทุกวิธีของอินสแตนซ์คลาส

ขอบเขตที่สืบทอดมาและการรวม Proc

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

Procs (รวมถึงบล็อกและแลมบ์ดาโดยการขยาย) ต่างกัน เมื่อใดก็ตามที่ proc ถูกสร้างอินสแตนซ์ การเชื่อมโยงจะถูกสร้างขึ้นซึ่งรับช่วงการอ้างอิงไปยังตัวแปรในเครื่องในบริบทที่บล็อกถูกสร้างขึ้น

foo = 1
Proc.new { foo }.call # => 1

ในตัวอย่างนี้ เราตั้งค่าตัวแปรชื่อ foo ถึง 1 . ภายใน ออบเจ็กต์ Proc ที่สร้างในบรรทัดที่สองจะสร้างการเชื่อมโยงใหม่ เมื่อเรียกใช้ proc เราสามารถขอค่าของตัวแปรได้

เนื่องจากการเชื่อมโยงจะถูกสร้างขึ้นเมื่อมีการเริ่มต้น proc เราจึงไม่สามารถสร้าง proc ก่อนกำหนดตัวแปรได้ แม้ว่าบล็อกจะไม่ถูกเรียกจนกว่าจะมีการกำหนดตัวแปรแล้ว

proc = Proc.new { foo }
foo = 1
proc.call # => NameError (undefined local variable or method `foo' for main:Object)

การเรียก proc จะสร้าง NameError เนื่องจากตัวแปรไม่ได้ถูกกำหนดในการผูกของ proc ดังนั้น ควรกำหนดตัวแปรใดๆ ที่เข้าถึงใน proc ก่อนที่ proc จะถูกสร้างขึ้นหรือส่งผ่านเป็นอาร์กิวเมนต์

foo = 1
proc = Proc.new { foo }
foo = 2
proc.call # => 2

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

foo = 1
Proc.new { foo = 2 }.call
foo #=> 2

ในตัวอย่างนี้ เราจะเห็นว่า foo ตัวแปรชี้ไปที่วัตถุเดียวกันเมื่ออยู่ใน proc กับภายนอก เราสามารถอัปเดตภายใน proc เพื่อให้ตัวแปรภายนอกอัปเดตได้เช่นกัน

การผูกมัด

เพื่อติดตามขอบเขตปัจจุบัน Ruby ใช้ bindings ซึ่งสรุปบริบทการดำเนินการในแต่ละตำแหน่งในโค้ด binding เมธอดส่งคืน binding วัตถุที่อธิบายการผูกที่ตำแหน่งปัจจุบัน

foo = 1
binding.local_variables # => [:foo]

วัตถุโยงมีเมธอดชื่อ #local_variables ซึ่งส่งคืนชื่อของตัวแปรท้องถิ่นทั้งหมดที่มีอยู่ในขอบเขตปัจจุบัน

foo = 1
binding.eval("foo") # => 1

สามารถประเมินรหัสในการผูกโดยใช้ #eval กระบวนการ. ตัวอย่างข้างต้นไม่มีประโยชน์มากนัก เพียงเรียก foo ก็จะได้ผลเช่นเดียวกัน อย่างไรก็ตาม เนื่องจากการเชื่อมเป็นวัตถุที่ส่งต่อได้ จึงสามารถนำมาใช้กับสิ่งที่น่าสนใจกว่าได้ มาดูตัวอย่างกัน

ตัวอย่างชีวิตจริง

ตอนนี้เราได้เรียนรู้เกี่ยวกับการผูกมัดเพื่อความปลอดภัยในโรงรถของเราแล้ว เช่น นำมันออกไปที่เนินลาดแล้วเล่นหิมะ นอกเหนือจากการใช้การโยงภายในของ Ruby ทั่วทั้งภาษาแล้ว ยังมีบางสถานการณ์ที่มีการใช้ออบเจกต์การโยงอย่างชัดเจน ตัวอย่างที่ดีคือ ERB—ระบบเทมเพลตของ Ruby

require 'erb'
 
x = 1
 
def y
  2
end
 
template = ERB.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`")
template.result(binding) # => "x is 1, y() returns 2, self is `main`"

ในตัวอย่างนี้ เราสร้างตัวแปรชื่อ x , วิธีการที่เรียกว่า y และเทมเพลต ERB ที่อ้างอิงทั้งสองอย่าง จากนั้นเราก็ส่งการเชื่อมโยงปัจจุบันไปยัง ERB#result ซึ่งประเมินแท็ก ERB ในเทมเพลตและส่งคืนสตริงพร้อมตัวแปรที่กรอก

ภายใต้ประทุน ERB ใช้ Binding#eval เพื่อประเมินเนื้อหาของแท็ก ERB แต่ละรายการในขอบเขตของการผูกที่ผ่าน การนำไปใช้อย่างง่ายซึ่งใช้ได้กับตัวอย่างข้างต้นอาจมีลักษณะดังนี้:

class DiyErb
  def initialize(template)
    @template = template
  end
 
  def result(binding)
    @template.gsub(/<%=(.+?)%>/) do
      binding.eval($1)
    end
  end
end
 
x = 1
 
def y
  2
end
 
template = DiyErb.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`")
template.result(binding) # => "x is 1, y() returns 2, self is `main`"

DiyErb คลาสใช้สตริงเทมเพลตในการเริ่มต้น มัน #result method ค้นหาแท็ก ERB ทั้งหมดและแทนที่ด้วยผลลัพธ์ของการประเมินเนื้อหา ในการทำเช่นนั้น มันเรียก Binding#eval ในการเชื่อมโยงที่ผ่าน โดยมีเนื้อหาของแท็ก ERB

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

เราเสียเธอไปในป่าหรือเปล่า

เราหวังว่าคุณจะสนุกกับการเล่นสกีในป่าของเรา เราเจาะลึกเข้าไปในขอบเขตและการปิด หลังจากกลบเกลื่อนไปแล้ว เราหวังว่าเราจะไม่สูญเสียคุณในป่า โปรดแจ้งให้เราทราบหากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับการผูก หรือมีหัวข้อ Ruby อื่น ๆ ที่คุณต้องการเจาะลึก

ขอขอบคุณที่ติดตามเราและโปรดปิดการผูกมัดของคุณก่อนที่จะปล่อยให้นักพัฒนาซอฟต์แวร์คนต่อไป หากคุณชอบทริปมหัศจรรย์เหล่านี้ คุณอาจต้องการสมัครรับ Ruby Magic เพื่อรับอีเมลเมื่อเราเผยแพร่บทความใหม่เดือนละครั้ง