2010-01-07 3 views
10

Я постоянно нахожу, что я рассматриваю ненужный код в Ruby при использовании именованных аргументов для методов.Именованные аргументы как локальные переменные в Ruby

Возьмем, например, следующий код:

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" 

Что мне действительно не нравится этот код является то, что я имею принять арг и передать значения в локальных переменных идет против СУХИХ принципов и просто обычно занимая место в моих методах. И если я не использую локальные переменные и просто ссылаюсь на все переменные с синтаксисом args [: symbol], тогда код становится несколько неразборчивым.

Я попытался разработать решение этого вопроса, но не ударил кирпичную стену, поскольку не знаю, как определить локальные переменные, используя eval в рамках метода, или используя любую другую технику. Вот одна из многих попыток ниже, что приводит к ошибке

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" 

При выполнении этого кода я просто получить

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

Кто есть какие-либо идеи, как я мог бы упростить это вниз как-то так, что я не нужно запускать мои методы с именами аргументов с загрузками кода var = args [: var]?

Спасибо, Matthew O'Riordan

+0

Похоже, что вы действительно хотите здесь, это аргументы. Они существуют в Ruby 2.0. – Ajedi32

ответ

6

Я не верю, что есть какой-нибудь способ сделать это в Ruby (если кто-то приходит с одним, пожалуйста, дайте мне знать, и я буду обновлять или удалить этот ответ, чтобы отразить его !) - если локальная переменная еще не определена, нет возможности динамически определить ее с привязкой. Вы могли бы сделать что-то вроде orange, lemon, grapefruit = nil перед вызовом eval, но вы можете столкнуться с другими проблемами - например, если args [: orange] - это строка «Оранжевый», вы закончите оценку orange = Orange вашей текущей реализацией.

Вот то, что может работать, хотя, используя OpenStruct класс из стандартной библиотеки (по «может работать», я имею в виду «это до вашего чувства стиля ли a.orange является любой лучше, чем 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 

Если вам не нужен простой доступ к любому состоянию или методам на получателе этого метода, вы можете использовать instance_eval, как показано ниже.

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 

Можно даже сделать something tricky with method_missing (см here для более), чтобы разрешить доступ к «основному» объекту, но производительность, вероятно, не будет большой.

В целом, я думаю, что это, пожалуй, самый простой/читаемый, чтобы пойти с менее сухим первоначальным решением, которое беспокоило вас.

+0

Спасибо за ввод. Еще через несколько дней, пытаясь найти решение, я думаю, что ваше предложение сохранить его так, как сейчас, вероятно, лучше всего! Попытка добавления переменных в local_variables кажется невероятно трудной и просто не является чем-то, что поощряет Ruby. Я видел, что раньше было расширение Binding.caller (в http://extensions.rubyforge.org/rdoc/index.html), которое предоставило бы функциональность, чтобы получить привязку вызова и вставить локальные переменные, но это было осуждается. –

3

Я нашел обсуждение этого вопроса на ruby-talk-google, и это, кажется, оптимизация анализатора. Локальные переменные уже вычислены во время выполнения, так что local_variables уже задано в начале метода.

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

Таким образом Руби не нужно решать a, является ли метод или локальной переменной или этажерку во время выполнения, но можно смело предположить, что это локальная переменная.

(Для сравнения:. В Python locals() будет пустым в начале функции)

+0

Много хорошей информации в этой теме - хорошая находка. –

1

На моем блоге (ссылка в информации о пользователе), я просто пытался решить эту проблему обработки аккуратно. Я вдаваться в подробности, но суть моего решения является следующий вспомогательный метод:

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 

, который называется следующим образом:

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 

Я все еще пытаюсь выяснить, если есть любой способ получить мои результаты в local_variables или нет, но, как отмечали другие, Ruby не хочет этого делать. Полагаю, вы могли бы использовать трюк «с».

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

затем

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

но чувствует себя неудовлетворительно по нескольким причинам.

Мне нравится вышеупомянутое решение, потому что оно использует Struct вместо OpenStruct, что означает, что вам нужно меньше требовать, а то, что вы вернетесь, установлено до того, какие переменные обрабатываются.

6

Слияние Грега и ответы Санд:

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

Это похоже на довольно отличное решение и потенциально очень полезно – RubyFanatic

+0

Красивый и элегантный метод. Прекрасно работает. Спасибо, что поделился! – plang

0

Это не решает проблему, но я склонен делать

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

, как это довольно легко скопировать и вставить orange lemon grapefruit немного.

Если вы нашли двоеточия слишком много работы, вы могли бы сделать

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

я задавался вопросом, как сделать это сам сегодня. Мне не только понравился бы DRY мой код, но я бы тоже хотел получить подтверждение аргумента.

Я столкнулся с a blog post by Juris Galang, где он объяснил пару способов обращения с ним. Он опубликовал a gem that encapsulates his ideas, который выглядит интересным.

Смежные вопросы