Ruby программно вызывающий метод с переменным числом аргументов
Я пытаюсь сделать что-то похожее на это:
def foo(mode= :serial)
if (mode == :serial) then
self.send(:bar, "one_type")
else
self.send(:bar,"second_type",:T5)
end
end
Я, очевидно, могу напечатать это так.
Но недавно я попытался расширить его, добавив в него вторую функцию:
def foo(mode= :serial)
if (mode == :serial) then
self.send(:bar, "one_type")
self.send(:foobar, "one_type",20)
else
self.send(:bar,"second_type",:T5)
self.send(:foobar, "one_type",20,:T5)
end
end
Я все еще могу продолжать, как есть, но я подумал про себя: здесь есть шаблон, я должен абстрагировать аргументы в другой слой и сделать это проще.
Так что я хотел сделать что-то вроде этого:
arg_arr = [:bar, "one_type"]
arg_arr_2 = [:foobar, "one_type", 20]
if (mode == :serial) then
self.send(arg_arr)
self.send(arg_arr_2)
else
arg_arr << :T5
arg_arr2 << :T5
self.send(arg_arr)
self.send(arg_arr2 )
end
Я попробовал некоторые другие идеи, включающие.each,.inspect, но ничего, что могло бы сработать (обычной ошибкой было не преобразование массива в строку, что, я думаю, относится к тому факту, что он рассматривает массив как полное имя функции), Я могу сделать это, если я явно скажу "использовать элементы массива [0], [1] и т. Д., Но это просто кажется расточительным".
Есть ли способ достичь этого без написания кода, который жестко запрограммирован на количество аргументов?
3 ответа
Попробуй это
def foo(a, b)
puts a
puts b
end
array = ['bar', 'qux']
send(:foo, *array) # using send
foo(*array) # using the method name
Обе печати
bar
qux
Оператор сплат *
упаковывает или распаковывает массив.
Несколько лет назад я сделал то, что вы пытаетесь сейчас. С звездочкой перед параметром метода вы можете получить столько параметров, сколько вы хотите в функции. Таким образом, вам не нужно знать количество заданных параметров. Это называется сплат.
Отправьте ваши значения в виде массива со звездочкой впереди, и это будет работать.
Я проверил фоллинг с помощью консоли irb:
def test(*args)
puts args.inspect
end
my_args = [1, 2, 3]
self.send(:test, *my_args)
# [1, 2, 3]
# => nil
Или отправьте столько отдельных параметров, сколько хотите:
self.send(:test, 'a', 'b', 'c', 'd')
# ["a", "b", "c", "d"]
# => nil
Если у вас есть фиксированное количество параметров, это будет работать:
def test(arg1, arg2, arg3)
puts arg1.inspect
puts arg2.inspect
puts arg3.inspect
end
my_args = [1, 2, 3]
self.send(:test, *my_args)
# 1
# 2
# 3
# => nil
Во-первых, вы не должны использовать send
, Вы могли бы использовать public_send
, Method#call
или просто bar(...)
если вы знаете название метода.
Однородные параметры
Если параметры однородны (например, являются экземплярами одного и того же класса), вы можете просто поместить их в массив и использовать этот массив в качестве параметра:
def analyze_array(array)
puts "Elements : #{array}"
puts "Length : #{array.size}"
puts "Sum : #{array.inject(:+)}"
puts
end
analyze_array([1,2,3])
analyze_array([1,2,3,4,5])
Это выводит:
Elements : [1, 2, 3]
Length : 3
Sum : 6
Elements : [1, 2, 3, 4, 5]
Length : 5
Sum : 15
пример
Рефакторинг вашего кода немного может стать:
arg_arr = [:bar, 1]
arg_arr_2 = [:foobar, 1, 2]
def bar(array)
puts " bar with one parameter : #{array}"
end
def foobar(array)
puts " foobar with one parameter : #{array}"
end
[:serial, :parallel].each do |mode|
puts "Mode : #{mode}"
[arg_arr, arg_arr_2].each do |method_and_args|
method_name, *args = method_and_args
args << 3 if mode != :serial
method(method_name).call(args)
end
end
Это выводит:
Mode : serial
bar with one parameter : [1]
foobar with one parameter : [1, 2]
Mode : parallel
bar with one parameter : [1, 3]
foobar with one parameter : [1, 2, 3]
Гетерогенные параметры
Для неизвестного числа параметров, которые могут принадлежать разным классам, вы можете использовать оператор splat ( документация):
def analyze_parameters(*params)
puts "Parameters : #{params}"
puts "Number : #{params.size}"
puts "Classes : #{params.map(&:class)}"
end
analyze_parameters('Test')
analyze_parameters(1, 'a', :b, [:c, :d])
Это выводит:
Parameters : ["Test"]
Number : 1
Classes : [String]
Parameters : [1, "a", :b, [:c, :d]]
Number : 4
Classes : [Fixnum, String, Symbol, Array]
Ваш пример становится:
arg_arr = [:bar, 1 ]
arg_arr_2 = [:foobar, 1, 'a']
def bar(*params)
puts " bar with multiple parameters : #{params}"
end
def foobar(*params)
puts " foobar with multiple parameters : #{params}"
end
[:serial, :parallel].each do |mode|
puts "Mode : #{mode}"
[arg_arr, arg_arr_2].each do |method_and_args|
method_name, *args = method_and_args
args << :b if mode != :serial
method(method_name).call(*args)
end
end
Это выводит:
Mode : serial
bar with multiple parameters : [1]
foobar with multiple parameters : [1, "a"]
Mode : parallel
bar with multiple parameters : [1, :b]
foobar with multiple parameters : [1, "a", :b]