2010-01-07 16 views
10

Tôi thấy mình liên tục viết những gì tôi thấy là mã không cần thiết trong Ruby khi sử dụng các đối số được đặt tên cho các phương thức.Đối số được đặt tên làm biến cục bộ trong Ruby

Đưa ví dụ đoạn mã sau:

def my_method(args) 
    orange = args[:orange] 
    lemon = args[:lemon] 
    grapefruit = args[:grapefruit] 

    # code that uses 
    # orange, lemon & grapefruit in this format which is way prettier & concise than 
    # args[:orange] args[:lemon] args[:grapefruit] 

    puts "my_method variables: #{orange}, #{lemon}, #{grapefruit}" 
end 
my_method :orange => "Orange", :grapefruit => "Grapefruit" 

Những gì tôi thực sự không thích về mã này là tôi phải lấy args và vượt qua các giá trị vào các biến địa phương đi ngược lại nguyên tắc DRY và chỉ thường chiếm không gian trong các phương pháp của tôi. Và nếu tôi không sử dụng các biến cục bộ và chỉ tham chiếu đến tất cả các biến với cú pháp args [: symbol] thì mã đó sẽ trở nên hơi không đọc được.

Tôi đã cố gắng giải quyết vấn đề này nhưng vẫn giữ được bức tường gạch vì tôi không biết cách xác định biến cục bộ bằng cách sử dụng eval trong phạm vi của phương thức hoặc sử dụng bất kỳ kỹ thuật nào khác. Đây là một trong nhiều nỗ lực dưới đây, mà kết quả trong một lỗi

def my_method_with_eval(args) 
    method_binding = binding 
    %w{ orange lemon grapefruit}.each { |variable| eval "#{variable} = args[:#{variable}]", method_binding; } 

    # code that uses 
    # orange, lemon & grapefruit in this format which is way prettier & concise than 
    # args[:orange] args[:lemon] args[:grapefruit] 

    puts "my_method_with_eval variables: #{orange}, #{lemon}, #{grapefruit}" 
end 
my_method_with_eval :orange => "Orange", :grapefruit => "Grapefruit" 

Khi chạy mã mà tôi chỉ đơn giản là có được

NameError: undefined local variable or method ‘orange’ for main:Object method my_method_with_eval in named_args_to_local_vars at line at top level in named_args_to_local_vars at line 9

Bất cứ ai có bất kỳ ý tưởng làm thế nào tôi có thể đơn giản hóa này xuống bằng cách nào đó để tôi không phải bắt đầu các phương thức đối số được đặt tên của tôi với các tải của var = args [: var] code?

Cảm ơn, Matthew O'Riordan

+0

Âm thanh như những gì bạn thực sự muốn ở đây là các đối số được đặt tên. Những thứ này tồn tại trong Ruby 2.0. – Ajedi32

Trả lời

6

Tôi không tin rằng có cách nào để làm điều này trong Ruby (nếu có ai đi lên với một, xin vui lòng cho tôi biết, và tôi sẽ cập nhật hoặc xóa câu trả lời này để phản ánh nó !) - nếu biến cục bộ chưa được xác định, không có cách nào để tự động xác định biến đó bằng liên kết. Bạn có thể tưởng tượng làm một cái gì đó như orange, lemon, grapefruit = nil trước khi gọi eval, nhưng bạn có thể chạy vào các vấn đề khác - ví dụ, nếu args [: orange] là chuỗi "Orange", bạn sẽ kết thúc đánh giá orange = Orange với thực hiện hiện tại của bạn.

Dưới đây là một cái gì đó mà có thể làm việc, tuy nhiên, bằng cách sử dụng lớp OpenStruct từ thư viện chuẩn (bởi "có thể làm việc", tôi có nghĩa là "nó tùy thuộc vào phong cách của bạn cho dù a.orange là bất kỳ đẹp hơn args[:orange]"):

require 'ostruct' 

def my_method_with_ostruct(args) 
    a = OpenStruct.new(args) 
    puts "my_method_with_ostruct variables: #{a.orange}, #{a.lemon}, #{a.grapefruit}" 
end 

Nếu bạn không cần truy cập dễ dàng vào bất kỳ trạng thái hoặc phương pháp nào của người nhận phương pháp này, bạn có thể sử dụng instance_eval, như sau.

def my_method_with_instance_eval(args) 
    OpenStruct.new(args).instance_eval do 
    puts "my_method_with_instance_eval variables: #{orange}, #{lemon}, #{grapefruit}" 
    end 
end 

Bạn thậm chí có thể làm something tricky with method_missing (xem here để biết thêm) để cho phép truy cập vào các đối tượng "chính", nhưng hiệu suất có lẽ sẽ không thể tuyệt vời.

Tất cả trong tất cả, tôi nghĩ rằng nó có thể đơn giản nhất/dễ đọc để đi với giải pháp ban đầu ít DRY làm phiền bạn.

+0

Cảm ơn bạn đã nhập. Sau một vài ngày cố gắng tìm một giải pháp, tôi nghĩ rằng đề xuất của bạn để giữ cho nó giống như nó bây giờ có lẽ là tốt nhất! Cố gắng thêm các biến vào local_variables có vẻ là vô cùng khó khăn và không chỉ là điều mà Ruby khuyến khích. Tôi đã thấy rằng có sử dụng được một phần mở rộng Binding.caller (trong http://extensions.rubyforge.org/rdoc/index.html) mà sẽ cung cấp chức năng để có được sự ràng buộc gọi điện thoại và chèn các biến địa phương, nhưng đã được không được chấp nhận. –

3

Tôi đã tìm thấy một cuộc thảo luận về điều này trên ruby-talk-google và dường như đó là một tối ưu hóa của trình phân tích cú pháp. Các biến cục bộ đã được tìm ra trong thời gian chạy sao cho các biến cục bộ đã được đặt ở đầu phương thức.

def meth 
    p local_variables 
    a = 0 
    p local_variables 
end 
meth 
# => 
[:a] 
[:a] 

Bằng cách đó Ruby không cần phải quyết định xem a là một phương pháp hay một biến địa phương hoặc không có điều gì trong thời gian chạy nhưng có thể yên tâm rằng đó là một biến địa phương.

(Để so sánh:. Trong Python locals() sẽ được sản phẩm nào vào lúc bắt đầu của hàm)

+0

Rất nhiều thông tin tốt trong chủ đề đó - tìm tốt. –

1

Tại blog của tôi (xem liên kết trong thông tin người dùng), tôi chỉ cố gắng để giải quyết xử lý vấn đề này gọn gàng. Tôi đi vào chi tiết hơn ở đó, nhưng cốt lõi của giải pháp của tôi là phương pháp helper sau:

def collect_named_args(given, expected) 
    # collect any given arguments that were unexpected 
    bad = given.keys - expected.keys 

    # if we have any unexpected arguments, raise an exception. 
    # Example error string: "unknown arguments sonething, anyhting" 
    raise ArgumentError, 
     "unknown argument#{bad.count > 1 ? 's' : ''}: #{bad.join(', ')}", 
     caller unless bad.empty? 

    Struct.new(*expected.keys).new(
     *expected.map { |arg, default_value| 
      given.has_key?(arg) ? given[arg] : default_value 
     } 
    ) 
end # def collect_named_args 

được gọi như sau:

def foo(arguments = {}) 
    a = collect_named_args(arguments, 
     something: 'nothing', 
     everything: 'almost', 
     nothing:  false, 
     anything: 75) 

    # Do something with the arguments 
    puts a.anything 
end # def foo 

Tôi vẫn đang cố gắng tìm ra nếu có bất kỳ cách nào để có được kết quả của tôi vào local_variables hay không - nhưng như những người khác đã lưu ý, Ruby không muốn làm điều đó. Bạn có thể sử dụng "với" lừa, tôi giả sử.

module Kernel 
    def with(object, &block) 
    object.instance_eval &block 
    end 
end 

sau đó

with(a) do 
    # Do something with arguments (a) 
    put anything 
end 

nhưng mà cảm thấy không hài lòng vì nhiều lý do.

Tôi thích giải pháp trên vì nó sử dụng cấu trúc thay vì OpenStruct, có nghĩa là một yêu cầu ít hơn và những gì bạn nhận được được đặt theo các biến đang được xử lý.

6

Merge các câu trả lời Sand của Greg và:

require 'ostruct' 

def my_method(args = {}) 
    with args do 
    puts a 
    puts b 
    end 
end 

def with(args = {}, &block) 
    OpenStruct.new(args).instance_eval(&block) 
end 

my_method(:a => 1, :b => 2) 
+0

Điều này có vẻ như một giải pháp khá tiện lợi và có khả năng rất hữu ích – RubyFanatic

+0

Phương pháp đẹp và thanh lịch. Hoạt động tuyệt vời. Cám ơn vì đã chia sẻ! – plang

0

này không giải quyết được vấn đề, nhưng tôi có xu hướng làm

orange, lemon, grapefruit = [:orange, :lemon, :grapefruit]. 
map{|key| args.fetch(key)} 

vì nó khá dễ dàng để sao chép và dán các bit orange lemon grapefruit.

Nếu bạn tìm thấy những dấu hai chấm quá nhiều công việc, bạn có thể làm

orange, lemon, grapefruit = %w{orange, lemon, grapefruit}. 
map{|str| str.gsub(",", "").to_sym}.map{|key| args.fetch(key)} 
0

tôi thấy mình tự hỏi làm thế nào để làm điều này bản thân mình ngày hôm nay. Không chỉ tôi muốn DRY lên mã của tôi, nhưng tôi muốn có xác nhận đối số, quá.

Tôi đã xem qua số a blog post by Juris Galang nơi anh ấy giải thích một vài cách để xử lý. Anh ấy đã xuất bản a gem that encapsulates his ideas trông thú vị.

Các vấn đề liên quan