2009-03-07 31 views
22

Tôi muốn thực hiện một số phản ánh khá nặng trong Ruby. Tôi muốn tạo ra một chức năng trả về tên của các đối số của các chức năng gọi khác nhau cao hơn ngăn xếp cuộc gọi (chỉ một cao hơn sẽ là đủ nhưng tại sao dừng lại ở đó?). Tôi có thể sử dụng Kernel.caller, chuyển đến tệp và phân tích cú pháp danh sách đối số nhưng điều đó sẽ rất xấu và không đáng tin cậy.Cách lấy tên đối số bằng cách sử dụng sự phản chiếu

Chức năng mà tôi muốn sẽ làm việc theo cách sau:

module A 
    def method1(tuti, fruity) 
    foo 
    end 
    def method2(bim, bam, boom) 
    foo 
    end 
    def foo 
    print caller_args[1].join(",") #the "1" mean one step up the call stack 
    end 

end 

A.method1 
#prints "tuti,fruity" 
A.method2 
#prints "bim, bam, boom" 

tôi sẽ không quan tâm sử dụng ParseTree hoặc một số công cụ tương tự cho công việc này nhưng nhìn vào cây phân tích cú pháp, nó không phải là rõ ràng làm thế nào để sử dụng nó vì mục đích này. Tạo một phần mở rộng C như this là một khả năng khác nhưng nó sẽ là tốt đẹp nếu ai đó đã làm nó cho tôi.

Tôi có thể thấy rằng có thể tôi sẽ cần một số loại tiện ích mở rộng C. Tôi cho rằng điều đó có nghĩa là câu hỏi của tôi là sự kết hợp của phần mở rộng C sẽ làm việc dễ dàng nhất. Tôi không nghĩ rằng người gọi + ParseTree sẽ là đủ của mình. Theo như lý do tại sao tôi muốn làm điều này, thay vì nói "gỡ lỗi tự động", có lẽ tôi nên nói rằng tôi muốn sử dụng chức năng này để tự động kiểm tra các điều kiện gọi và trả lại các chức năng:

def add x, y 
    check_positive 
    return x + y 
end 

Trường hợp check_positive sẽ ném một ngoại lệ nếu xy không dương. Rõ ràng, sẽ có nhiều điều hơn thế nhưng hy vọng điều này mang lại đủ động lực.

+0

Dường như bạn muốn xác định thứ tự thực hiện và lập luận. Có lý do nào mà bạn không thể sử dụng tính năng ghi nhật ký không? Bạn thậm chí có thể làm một số metaprogramming để mở tất cả các phương thức trong lớp và tự động đăng nhập các đối số để làm những gì bạn muốn. – runako

Trả lời

15

Tôi đề nghị bạn hãy xem thư viện hành động-arg của Merb.

require 'rubygems' 
require 'merb' 

include GetArgs 

def foo(bar, zed=42) 
end 

method(:foo).get_args # => [[[:bar], [:zed, 42]], [:zed]] 

Nếu bạn không muốn phụ thuộc vào Merb, bạn có thể chọn và chọn phần tốt nhất từ ​​the source code in github.

4

Tôi có một phương pháp khá tốn kém và chỉ gần như hoạt động.

$shadow_stack = [] 

set_trace_func(lambda { 
    |event, file, line, id, binding, classname| 
    if event == "call" 
    $shadow_stack.push(eval("local_variables", binding)) 
    elsif event == "return" 
    $shadow_stack.pop 
    end 
}) 

def method1(tuti, fruity) 
    foo 
end 
def method2(bim, bam, boom) 
    foo 
    x = 10 
    y = 3 
end 

def foo 
    puts $shadow_stack[-2].join(", ") 
end 

method1(1,2) 
method2(3,4,4) 

Đầu ra

tuti, fruity 
bim, bam, boom, x, y 

tôi tò mò là tại sao bạn muốn chức năng như vậy một cách tổng quát như vậy.

Tôi tò mò về cách bạn cho rằng chức năng này sẽ cho phép gỡ lỗi tự động? Bạn vẫn cần phải tiêm các cuộc gọi đến hàm "foo" của bạn. Trên thực tế, một cái gì đó dựa trên set_trace_func có nhiều khả năng tự động hơn vì bạn không cần phải chạm vào mã hiện có. Thật vậy, đây là cách debug.rb được thực hiện, về set_trace_func.

Các giải pháp cho câu hỏi chính xác của bạn thực sự về cơ bản, như bạn đã phác thảo. sử dụng trình gọi + phân tích cú pháp hoặc mở tệp và lấy dữ liệu theo cách đó. Không có khả năng phản chiếu nào mà tôi biết sẽ cho phép bạn lấy tên của các đối số. Bạn có thể phê duyệt giải pháp của tôi bằng cách lấy đối tượng phương thức liên quan và gọi #arity để suy ra những gì của local_variables là các đối số, nhưng mặc dù nó xuất hiện kết quả của hàm đó được sắp xếp, tôi không chắc chắn là an toàn để dựa vào điều đó. Nếu bạn không nhớ tôi hỏi, một khi bạn có dữ liệu và giao diện bạn mô tả, bạn sẽ làm gì với nó? Tự động gỡ lỗi không phải là những gì ban đầu đến tâm trí khi tôi tưởng tượng sử dụng cho chức năng này, mặc dù có lẽ nó là thất bại của trí tưởng tượng trên một phần của tôi.


Aha!

Tôi sẽ tiếp cận sự khác biệt này sau đó. Có một số thư viện ruby ​​để thiết kế theo hợp đồng, bao gồm hợp đồng ruby, rdbc, v.v.

Một lựa chọn khác là viết một cái gì đó như:

def positive 
    lambda { |x| x >= 0 } 
end 

def any 
    lambda { |x| true } 
end 

class Module 
    def define_checked_method(name, *checkers, &body) 
    define_method(name) do |*args| 
     unless checkers.zip(args).all? { |check, arg| check[arg] } 
     raise "bad argument" 
     end 
     body.call(*args) 
    end 
    end 
end 

class A 
    define_checked_method(:add, positive, any) do |x, y| 
    x + y 
    end 
end 

a = A.new 
p a.add(3, 2) 
p a.add(3, -1) 
p a.add(-4, 2) 

Đầu ra

5 
2 
checked_rb.rb:13:in `add': bad argument (RuntimeError) 
     from checked_rb.rb:29 

Tất nhiên điều này có thể được thực hiện phức tạp hơn nhiều, và thực sự đó là một số những gì các thư viện tôi đã đề cập được cung cấp, nhưng có lẽ đây là một cách để giúp bạn có được nơi bạn muốn đi mà không nhất thiết phải lấy con đường bạn dự định sử dụng để đến đó?

+0

Trong thực tế, phương pháp bạn mô tả rõ ràng không phân biệt đối số từ biến địa phương trong khi cũng không hoạt động tự động –

+0

Vâng, nó hoạt động để thiết kế theo hợp đồng nhưng có vẻ hơi phức tạp so với chỉ có một hàm duy nhất bạn có thể gọi . Lý tưởng là để có thể * dễ dàng * thêm và loại bỏ các kiểm tra. –

-3

Trong thực tế, phương pháp mà bạn mô tả rõ ràng thất bại trong việc phân biệt đối số từ biến cục bộ trong khi cũng thất bại trong việc làm việc tự động

Đó là bởi vì những gì bạn đang cố gắng làm không phải là một cái gì đó mà được hỗ trợ. Có thể (mọi thứ đều có thể có trong ruby), nhưng không có cách nào được ghi chép hay biết cách làm.

Hoặc bạn có thể đánh giá ngược lại như những gì được đề xuất, hoặc bạn có thể phá vỡ trình biên dịch C của bạn và hack mã nguồn cho ruby. Tôi khá tự tin rằng không có cách nào khác để làm điều này.

39

Trong Ruby 1.9.2, bạn trivially có thể nhận được danh sách tham số của bất kỳ Proc (và do đó tất nhiên cũng của bất kỳ Method hoặc UnboundMethod) với Proc#parameters:

A.instance_method(:method1).parameters # => [[:req, :tuti], [:req, :fruity]] 

Định dạng là một mảng của cặp ký hiệu: loại (bắt buộc, tùy chọn, phần còn lại, khối) và tên.

Đối với các định dạng mà bạn muốn, hãy thử

A.instance_method(:method1).parameters.map(&:last).map(&:to_s) 
    # => ['tuti', 'fruity'] 

Tất nhiên, điều đó vẫn không cung cấp cho bạn truy cập cho người gọi, mặc dù.

+0

Điều này cũng không trả lại giá trị mặc định. Ví dụ: 'def person (tên:" calvin ")'. 'method.parameters' sẽ cung cấp cho' [[: key,: name]] '. –

+2

Giá trị mặc định được đánh giá động, bạn không thể nhận được thông tin tĩnh về chúng. Điều gì sẽ 'def foo (par = rand()); phương thức (: foo) .parameters' return? –

+0

'instance_method' có nghĩa là gì ở đây? – fraxture

1

nếu bạn muốn giá trị cho các giá trị mặc định cũng vậy, có những "lập luận" viên ngọc

$ gem install rdp-arguments 
$ irb 
>> require 'arguments' 
>> require 'test.rb' # class A is defined here 
>> Arguments.names(A, :go) 
Các vấn đề liên quan