Аргументы splat сопоставляются с параметрами метода

Мы создаем метод с разбросанными аргументами и вызываем для него Method#parameters:

def splatter(x, *y, z); end

params = method(:splatter).parameters
  # => [[:req, :x], [:rest, :y], [:req, :z]]

Я ищу функцию f, которая будет отображать список аргументов в соответствующие им имена переменных. Функция должна быть достаточно гибкой, чтобы работать с любым другим методом с произвольно размещенными аргументами знака знака. Например:

args = [:one, :two, :three, :four]

f(params, args)
  # => [[:x, :one], [:y, :two], [:y, :three], [:z, :four]]

или что-то в этом роде (перевернутые элементы тоже подойдут). Я чувствую, что должно быть гибкое, элегантное решение, использующее inject или что-то в этом роде, но я не могу его придумать.


person rickyrickyrice    schedule 01.10.2013    source источник
comment
Интересный вопрос. Для произвольных методов вы можете что-то сделать с информацией, полученной из объекта последовательности инструкций (RubyVM::InstructionSequence.of(method(:splatter))). Если вы вызываете to_a, 12-й элемент содержит индексы различных конфигураций аргументов. К сожалению, я слишком устал, чтобы думать об этом дальше, но, надеюсь, это может быть полезно.   -  person Zach Kemp    schedule 01.10.2013
comment
Хорошо, я не знал, что вы можете это сделать... Мне придется копнуть глубже в InstructionSequence, чтобы увидеть, какую информацию я могу из нее извлечь. Спасибо!   -  person rickyrickyrice    schedule 01.10.2013
comment
Если вы ищете тестовые примеры, вы можете найти этот мой ответ полезным.   -  person Jörg W Mittag    schedule 01.10.2013


Ответы (3)


def f(params,*args)
    # elements to be assigned to splat parameter
    splat = args.count - params.count + 1

    # will throw an error if splat < 0 as that means not enough inputs given        
    params.map{ |p|     

            [ p[1] , ( p.first == :rest ? args.shift(splat) : args.shift  ) ]

           }
end

Примеры

def splatter(x,*y,z)
    # some code
end

f(method(:splatter).parameters, 1,2,3,4)
#=>[[:x, 1], [:y, [2, 3]], [:z, 4]]

def splatter(x,y,*z)
    # some code
end

f(method(:splatter).parameters, 1,2,3,4)
# => [[:x, 1], [:y, 2], [:z, [3, 4]]]

def splatter(x,*z)
    # some code
end

f(method(:splatter).parameters, 1)
# => [[:x, 1], [:z, []]]
person tihom    schedule 01.10.2013
comment
Я случайно понизил ваш вопрос, но не могу удалить свой голос, если вы не отредактируете свой ответ. Простите за это! - person Patrick Oscity; 01.10.2013
comment
Я думаю, это определенно шаг в правильном направлении. Это не совсем тот ответ, который я хочу, но было бы несложно его скорректировать. Я подожду еще немного, чтобы посмотреть, может ли кто-нибудь придумать что-нибудь еще. Назовите это догадкой, но я действительно чувствую, что есть более простое решение. - person rickyrickyrice; 01.10.2013
comment
@rickyrickyrice его можно изменить в соответствии с вашим форматом. Не был уверен, как должны выглядеть результаты, если аргумент splat равен blank или []? - person tihom; 01.10.2013
comment
Очень хорошо! Читается как книга. Сплат, я люблю тебя. - person Cary Swoveland; 06.10.2013

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

def resolve(parameters,args)
  param_list = parameters.map do |type,name|
    splat = '*' if type == :rest
    "#{splat}#{name}"
  end.join(',')

  source = ""
  source << "->(#{param_list}) do\n"
  source << "  res = []\n"
  parameters.each do |type,name|
    if type == :rest
      source << "  res += #{name}.map {|v| [:'#{name}',v] }\n"
    else
      source << "  res << [:'#{name}',#{name}]\n"
    end
  end
  source << "end"

  eval(source).call(*args)
end

Пример:

params = ->(x,*y,z){}.parameters
resolve(params,[:one, :two, :three, :four])
#=> [[:x, :one], [:y, :two], [:y, :three], [:z, :four]]

За кулисами был сгенерирован следующий код:

->(x,*y,z) do
  res = []
  res << [:'x',x]
  res += y.map {|v| [:'y',v] }
  res << [:'z',z]
end

Другой пример с двумя аргументами, сначала splat:

params = ->(*x,y){}.parameters
resolve(params,[:one, :two, :three, :four])
#=> [[:x, :one], [:x, :two], [:x, :three], [:y, :four]]

С сгенерированным кодом

->(*x,y) do
  res = []
  res += x.map {|v| [:'x',v] }
  res << [:'y',y]
end
person Patrick Oscity    schedule 01.10.2013
comment
Очень интересный подход! Определенно дает мне некоторое представление о проблемной области. Это, вероятно, прозвучит раздражающе небрежно, но я действительно ищу что-то элегантное, что включает в себя функциональные операции, такие как сгиб, карта или застежка-молния. Но спасибо за предложение! - person rickyrickyrice; 01.10.2013
comment
хороший подход +1, потенциально может быть очень универсальным (включая ввод блоков, параметры параметров и т. д.), в той же строке есть старый плагин: merb-action-args - person tihom; 01.10.2013

Изменить: после моего первоначального замешательства:

def doit(params, args)
  rest_ndx = params.map(&:first).index(:rest)
  to_insert = [params[rest_ndx].last]*(args.size-params.size) if rest_ndx
  params = params.map(&:last)
  params.insert(rest_ndx,*to_insert) if rest_ndx
  params.zip(args)
end
person Cary Swoveland    schedule 01.10.2013
comment
Спасибо за ответ! Да, я бы предпочел что-то, что работает для произвольно размещенных знаков/обязательных параметров. Я обновлю вопрос. - person rickyrickyrice; 01.10.2013
comment
Я пересмотрел свой ответ (7 лет назад). - person Cary Swoveland; 26.07.2020