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]
Другие вопросы по тегам