Có thể có các Phương thức bên trong Phương thức không?


89

Tôi có một phương thức bên trong một phương thức. Phương pháp nội thất phụ thuộc vào một vòng lặp biến đang được chạy. đó có phải là ý tưởng tồi tệ không?


2
Bạn có thể chia sẻ mẫu mã hoặc ít nhất là tương đương hợp lý với những gì bạn đang cố gắng làm.
Aaron Scruggs

Câu trả lời:


165

CẬP NHẬT: Vì câu trả lời này có vẻ đã thu hút được một số sự quan tâm gần đây, tôi muốn chỉ ra rằng có một cuộc thảo luận về trình theo dõi vấn đề Ruby để loại bỏ tính năng được thảo luận ở đây, cụ thể là cấm có các định nghĩa phương thức bên trong thân phương thức .


Không, Ruby không có các phương thức lồng nhau.

Bạn có thể làm điều gì đó như sau:

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

Test1.new.meth1

Nhưng đó không phải là một phương pháp lồng nhau. Tôi nhắc lại: Ruby không có các phương thức lồng nhau.

Đây là một định nghĩa phương thức động. Khi bạn chạy meth1, phần thân của meth1sẽ được thực thi. Phần thân chỉ xảy ra để xác định một phương thức có tên meth2, đó là lý do tại sao sau khi chạy meth1một lần, bạn có thể gọi meth2.

Nhưng meth2định nghĩa ở đâu? Chà, rõ ràng nó không được định nghĩa là một phương thức lồng nhau, vì không có phương thức lồng nhau nào trong Ruby. Nó được định nghĩa là một phương thức thể hiện của Test1:

Test1.new.meth2
# Yay

Ngoài ra, nó rõ ràng sẽ được xác định lại mỗi khi bạn chạy 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

Tóm lại: không, Ruby không hỗ trợ các phương thức lồng nhau.

Cũng lưu ý rằng trong Ruby, các thân phương thức không thể là các bao đóng, chỉ các thân khối mới có thể. Điều này giúp loại bỏ khá nhiều trường hợp sử dụng chính cho các phương thức lồng nhau, vì ngay cả khi Ruby hỗ trợ các phương thức lồng nhau, bạn không thể sử dụng các biến của phương thức bên ngoài trong phương thức lồng nhau.


CẬP NHẬT TIẾP THEO: tại một sau sân khấu, sau đó, cú pháp này có thể tái sử dụng cho thêm các phương pháp lồng nhau để Ruby, mà sẽ hành xử theo cách tôi đã mô tả: họ sẽ được scoped phương pháp chứa của họ, tức là bên ngoài vô hình và không thể tiếp cận của phương pháp chứa của họ thân hình. Và có thể, họ sẽ có quyền truy cập vào phạm vi từ vựng của phương thức chứa của họ. Tuy nhiên, nếu bạn đọc phần thảo luận mà tôi đã liên kết ở trên, bạn có thể thấy rằng matz rất chống lại các phương thức lồng nhau (nhưng vẫn để loại bỏ các định nghĩa phương thức lồng nhau).


6
Tuy nhiên, bạn cũng có thể chỉ ra cách tạo một lambda đóng trong một phương thức cho DRYness hoặc để chạy đệ quy.
Phrogz

119
Tôi có cảm giác rằng Ruby có thể không có các phương thức lồng nhau.
Mark Thomas

16
@Mark Thomas: Tôi đã quên đề cập rằng Ruby không có các phương thức lồng nhau? :-) Nghiêm túc: lúc tôi đã viết câu trả lời này, đã có ba câu trả lời, mỗi một đơn trong đó tuyên bố rằng Ruby không có phương pháp lồng nhau. Một số câu trả lời trong số đó thậm chí còn có phiếu tán thành mặc dù sai một cách trắng trợn. Một thậm chí đã được OP chấp nhận, một lần nữa, mặc dù sai. Đoạn mã mà câu trả lời sử dụng để chứng minh rằng Ruby hỗ trợ các phương thức lồng nhau, thực sự chứng minh điều ngược lại, nhưng dường như cả người ủng hộ và OP đều không thực sự bận tâm đến việc kiểm tra. Vì vậy, tôi đã đưa ra một câu trả lời đúng cho mỗi câu trả lời sai. :-)
Jörg W Mittag

10
Khi bạn nhận ra rằng tất cả chỉ là hướng dẫn cho Kernel sửa đổi bảng và các phương thức, lớp và mô-đun đều chỉ là các mục nhập trong bảng và không thực sự có thật, bạn sẽ trở nên giống Neo khi anh ta nhìn thấy Ma trận trông như thế nào. Sau đó, bạn thực sự có thể trở thành triết học và nói rằng bên cạnh các phương pháp lồng nhau, thậm chí không có phương pháp nào. Thậm chí không có đại lý. Chúng là các chương trình trong ma trận. Ngay cả miếng bít tết ngon ngọt mà bạn đang ăn cũng chỉ là một món ăn trong bàn.
mydoghasworms

3
Không có phương pháp, mã của bạn chỉ là một mô phỏng trong The Matrix
bbozo

13

Trên thực tế, nó có thể. Bạn có thể sử dụng procs / lambda cho việc này.

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

2
Bạn không sai, nhưng câu trả lời của bạn là một giải pháp để đạt được các phương pháp lồng nhau. Trong khi thực tế, bạn chỉ đang sử dụng procs không phải là phương thức. Đó là một câu trả lời hay ngoài việc tuyên bố giải được "các phương pháp lồng nhau"
Brandon Buck

5

Không, không, Ruby có các phương thức lồng nhau. Kiểm tra điều này:

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 không phải là một phương thức, nó là một hàm / lambda / proc. Không có cá thể liên quan của bất kỳ lớp nào nên nó không phải là một phương thức.
Sami Samhuri

2

Bạn có thể làm điều gì đó như thế này

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

Điều này rất hữu ích khi bạn đang làm những việc như viết DSL yêu cầu chia sẻ phạm vi giữa các phương thức. Nhưng nếu không, bạn nên làm bất cứ điều gì khác, vì như các câu trả lời khác đã nói, innerđược xác định lại bất cứ khi nào outerđược gọi. Nếu bạn muốn hành vi này và đôi khi có thể, đây là một cách tốt để thực hiện.


2

Cách thức của Ruby là giả mạo nó bằng các bản hack khó hiểu khiến một số người dùng tự hỏi "Làm thế nào mà cái này lại hoạt động được?", Trong khi những người ít tò mò hơn sẽ chỉ đơn giản là ghi nhớ cú pháp cần thiết để sử dụng thứ đó. Nếu bạn đã từng sử dụng Rake hoặc Rails, bạn sẽ thấy loại này.

Đây là một vụ hack:

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

Điều đó làm là xác định một phương thức cấp cao nhất tạo ra một lớp và chuyển nó đến một khối. Lớp sử dụng method_missingđể giả vờ rằng nó có một phương thức với tên bạn đã chọn. Nó "triển khai" phương thức bằng cách gọi lambda mà bạn phải cung cấp. Bằng cách đặt tên cho đối tượng bằng tên một chữ cái, bạn có thể giảm thiểu số lượng nhập thêm mà nó yêu cầu (giống như điều mà Rails thực hiện với nó schema.rb). mletđược đặt tên theo dạng Common Lisp flet, ngoại trừ trường hợp flà viết tắt của "function", mlà viết tắt của "method".

Bạn sử dụng nó như thế này:

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

Có thể tạo ra một cấu trúc tương tự cho phép xác định nhiều hàm bên trong mà không cần lồng bổ sung, nhưng điều đó đòi hỏi một kiểu hack thậm chí còn xấu hơn mà bạn có thể tìm thấy trong quá trình triển khai của Rake hoặc Rspec. Tìm hiểu cách thức let!hoạt động của Rspec sẽ giúp bạn có một chặng đường dài để có thể tạo ra một thứ ghê tởm khủng khiếp như vậy.


-3

:-D

Ruby có các phương thức lồng nhau, chỉ có điều chúng không làm những gì bạn mong đợi

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
Đây không phải là phương pháp lồng nhau ... hãy xem câu trả lời của Jörg W Mittag để hiểu rõ.
Hardik
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.