เป็นไปได้หรือไม่ที่จะมีวิธีการภายใน Methods?


89

ฉันมีวิธีการภายในของวิธีการ วิธีการตกแต่งภายในขึ้นอยู่กับลูปตัวแปรที่กำลังรัน เป็นความคิดที่ไม่ดี?


2
คุณสามารถแบ่งปันตัวอย่างโค้ดหรืออย่างน้อยก็เทียบเท่ากับสิ่งที่คุณพยายามทำ
Aaron Scruggs

คำตอบ:


165

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


ไม่ Ruby ไม่มีเมธอดซ้อนกัน

คุณสามารถทำสิ่งนี้:

class Test1
  def meth1
    def meth2
      puts "Yay"
    end
    meth2
  end
end

Test1.new.meth1

แต่นั่นไม่ใช่วิธีการซ้อนกัน ฉันพูดซ้ำ: Ruby ไม่มีวิธีการซ้อนกัน

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

แต่meth2กำหนดไว้ที่ไหน? เห็นได้ชัดว่ามันไม่ได้ถูกกำหนดให้เป็นเมธอดซ้อนกันเนื่องจากไม่มีเมธอดซ้อนใน Ruby กำหนดเป็นวิธีการเช่นTest1:

Test1.new.meth2
# Yay

นอกจากนี้จะมีการกำหนดนิยามใหม่ทุกครั้งที่คุณวิ่งmeth1:

Test1.new.meth1
# Yay

Test1.new.meth1
# test1.rb:3: warning: method redefined; discarding old meth2
# test1.rb:3: warning: previous definition of meth2 was here
# Yay

กล่าวโดยย่อ: ไม่ Ruby ไม่สนับสนุนวิธีการซ้อนกัน

โปรดทราบว่าใน Ruby ไม่สามารถปิดเมธอดบอดี้ได้ แต่บล็อกบอดี้เท่านั้นที่ทำได้ นี้สวยมากกำจัดกรณีใช้งานที่สำคัญสำหรับวิธีการที่ซ้อนกันตั้งแต่แม้กระทั่งถ้าทับทิมสนับสนุนวิธีการซ้อนกันคุณจะไม่สามารถใช้ตัวแปรวิธีการนอกในวิธีการที่ซ้อนกัน


อัปเดตอย่างต่อเนื่อง: ในระยะต่อมาไวยากรณ์นี้อาจถูกนำมาใช้ใหม่เพื่อเพิ่มเมธอดที่ซ้อนกันให้กับ Ruby ซึ่งจะทำงานตามที่ฉันอธิบายไว้: พวกเขาจะถูกกำหนดขอบเขตไปยังเมธอดที่มีอยู่นั่นคือมองไม่เห็นและไม่สามารถเข้าถึงได้นอกเมธอดที่มีอยู่ ร่างกาย. และอาจเป็นไปได้ว่าพวกเขาสามารถเข้าถึงขอบเขตคำศัพท์ของวิธีการที่มีอยู่ อย่างไรก็ตามหากคุณอ่านการสนทนาที่ฉันเชื่อมโยงไว้ข้างต้นคุณจะสังเกตได้ว่า matz ต่อต้านเมธอดที่ซ้อนกันอย่างมาก (แต่ยังคงใช้สำหรับการลบคำจำกัดความของเมธอดที่ซ้อนกัน)


6
อย่างไรก็ตามคุณอาจแสดงวิธีสร้างแลมด้าปิดด้วยวิธีการแห้งหรือเรียกใช้การเรียกซ้ำ
Phrogz

119
ฉันรู้สึกว่า Ruby อาจไม่มีวิธีการซ้อนกัน
Mark Thomas

16
@Mark Thomas: ฉันลืมที่จะพูดถึงว่า Ruby ไม่ได้มีวิธีการซ้อนกัน? :-) อย่างจริงจัง: ในขณะที่ผมเขียนคำตอบนี้มีอยู่แล้วสามคำตอบทุกหนึ่งเดียวซึ่งอ้างว่าทับทิมจะมีวิธีการที่ซ้อนกัน บางคำตอบเหล่านั้นมีการโหวตเพิ่มขึ้นแม้ว่าจะผิดอย่างโจ่งแจ้งก็ตาม หนึ่งได้รับการยอมรับจาก OP อีกครั้งแม้จะผิดก็ตาม ข้อมูลโค้ดที่ใช้คำตอบเพื่อพิสูจน์ว่า Ruby รองรับวิธีการซ้อนกันจริง ๆ แล้วพิสูจน์ได้ว่าตรงกันข้าม แต่ดูเหมือนว่าทั้งผู้โหวตหรือ OP ไม่ใส่ใจที่จะตรวจสอบ ดังนั้นฉันจึงให้คำตอบที่ถูกหนึ่งข้อสำหรับทุกคำตอบที่ผิด :-)
Jörg W Mittag

10
เมื่อคุณรู้ว่าทั้งหมดนี้เป็นเพียงคำแนะนำสำหรับ Kernel ที่ปรับเปลี่ยนตารางและวิธีการและคลาสและโมดูลทั้งหมดเป็นเพียงรายการในตารางไม่ใช่ของจริงคุณจะกลายเป็นเหมือน Neo เมื่อเขาเห็นว่า Matrix มีลักษณะอย่างไร จากนั้นคุณอาจกลายเป็นนักปรัชญาและบอกว่านอกจากวิธีการซ้อนกันแล้วยังไม่มีแม้แต่วิธีการ ไม่มีแม้แต่ตัวแทน เป็นโปรแกรมในเมทริกซ์ แม้แต่สเต็กเนื้อฉ่ำที่คุณกำลังรับประทานอยู่ก็เป็นเพียงรายการในโต๊ะเท่านั้น
mydoghasworms

3
ไม่มีวิธีการใด ๆ รหัสของคุณเป็นเพียงการจำลองใน The Matrix
bbozo

13

จริงๆแล้วมันเป็นไปได้ คุณสามารถใช้ procs / lambda สำหรับสิ่งนี้

def test(value)
  inner = ->() {
    value * value
  }
  inner.call()
end

2
คุณไม่ผิด แต่คำตอบของคุณเป็นคำตอบเพื่อให้บรรลุวิธีการซ้อนกัน ในความเป็นจริงคุณแค่ใช้ procs ซึ่งไม่ใช่วิธีการ เป็นคำตอบที่ดีนอกการอ้างว่าแก้ "วิธีการซ้อน"
Brandon Buck

5

ไม่ไม่ Ruby มีวิธีการซ้อนกัน ตรวจสอบสิ่งนี้:

def outer_method(arg)
    outer_variable = "y"
    inner_method = lambda {
      puts arg
      puts outer_variable
    }
    inner_method[]
end

outer_method "x" # prints "x", "y"

9
inner_method ไม่ใช่ method แต่เป็น function / lambda / proc ไม่มีอินสแตนซ์ที่เกี่ยวข้องของคลาสใด ๆ ดังนั้นจึงไม่ใช่วิธีการ
Sami Samhuri

2

คุณสามารถทำสิ่งนี้ได้

module Methods
  define_method :outer do 
    outer_var = 1
    define_method :inner do
      puts "defining inner"
      inner_var = outer_var +1
    end
    outer_var
  end
  extend self
end

Methods.outer 
#=> defining inner
#=> 1
Methods.inner 
#=> 2

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


2

วิธี Ruby คือการปลอมแปลงด้วยการแฮ็กที่สับสนซึ่งจะทำให้ผู้ใช้บางคนสงสัยว่า "วิธีนี้ใช้งานได้อย่างไร" ในขณะที่คนที่อยากรู้อยากเห็นน้อยลงจะจดจำไวยากรณ์ที่จำเป็นในการใช้สิ่งนี้ หากคุณเคยใช้ Rake หรือ Rails คุณเคยเห็นสิ่งนี้มาแล้ว

นี่คือการแฮ็ก:

def mlet(name,func)
  my_class = (Class.new do
                def initialize(name,func)
                  @name=name
                  @func=func
                end
                def method_missing(methname, *args)
                  puts "method_missing called on #{methname}"
                  if methname == @name
                    puts "Calling function #{@func}"
                    @func.call(*args)
                  else
                    raise NoMethodError.new "Undefined method `#{methname}' in mlet"
                  end
                end
              end)
  yield my_class.new(name,func)
end

สิ่งที่ทำคือกำหนดวิธีการระดับบนสุดที่สร้างคลาสและส่งต่อไปยังบล็อก ชั้นเรียนใช้method_missingเพื่อหลอกว่ามีวิธีการที่มีชื่อที่คุณเลือก มัน "ดำเนินการ" วิธีการโดยเรียกแลมบ์ดาที่คุณต้องระบุ ด้วยการตั้งชื่อวัตถุด้วยชื่อตัวอักษรเดียวคุณสามารถลดจำนวนการพิมพ์พิเศษที่ต้องการได้ (ซึ่งเป็นสิ่งเดียวกับที่ Rails ทำในschema.rbวัตถุนั้น) mletได้รับการตั้งชื่อตามรูปแบบ Common Lisp fletยกเว้นที่fย่อมาจาก "function" mย่อมาจาก "method"

คุณใช้สิ่งนี้:

def outer
   mlet :inner, ->(x) { x*2 } do |c|
     c.inner 12
   end
end

เป็นไปได้ที่จะสร้างการคุมกำเนิดที่คล้ายกันซึ่งช่วยให้สามารถกำหนดฟังก์ชันภายในได้หลายฟังก์ชันโดยไม่ต้องซ้อนกันเพิ่มเติม แต่ต้องใช้การแฮ็กประเภทที่น่าเกลียดยิ่งกว่าที่คุณอาจพบในการใช้งานของ Rake หรือ Rspec การหาคำตอบว่าlet!ผลงานของ Rspec จะช่วยให้คุณสามารถสร้างสิ่งที่น่ารังเกียจที่น่าสยดสยองได้อย่างไร


-3

:-D

Ruby มีวิธีการซ้อนกัน แต่พวกมันไม่ทำในสิ่งที่คุณคาดหวัง

1.9.3p484 :001 > def kme; 'kme'; def foo; 'foo'; end; end              
 => nil 
1.9.3p484 :003 >   self.methods.include? :kme
 => true 
1.9.3p484 :004 > self.methods.include? :foo
 => false 
1.9.3p484 :005 > kme
 => nil 
1.9.3p484 :006 > self.methods.include? :foo
 => true 
1.9.3p484 :007 > foo
 => "foo" 

4
นี่ไม่ใช่วิธีการซ้อน ... ดูคำตอบของJörg W Mittagเพื่อความเข้าใจที่ชัดเจน
Hardik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.