Как передать все параметры метода send - PullRequest
0 голосов
/ 19 февраля 2019

Вопрос

Когда в методе, как я могу передать все параметры, предоставляемые как часть отправки метода в отправку другого метода?Все параметры должны быть названы в том смысле, что нет никаких заполнителей / перехватчиков, таких как *args, но они могут представлять собой сочетание аргументов с ключевыми словами и аргументов без ключевых слов.Кроме того, внутренний метод отправки не является super.

Пример (псевдокод)

def some_method(a_parameter, a_named_parameter:)
  ...do something...
  some_other_method([send with original parameters])
  ...do something else...
end

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

Есть ли способ доступа к аргументам методав Ruby? спросили 7 лет назад.

Гадкий хак

На основании того, что я нашел, можно сделать что-то подобное для параметров ключевых слов:

def some_method(a_named_parameter:, another_named_parameter:)
  ...do something...

  params = method(__method__)
           .parameters
           .map { |p| [p[1], binding.local_variable_get(p[1])] }
           .to_h

  some_other_method(**params)
  ...do something else...
end

И это для параметров без ключевых слов:

def some_method(a_named_parameter, another_named_parameter)
  ...do something...

  params = method(__method__)
           .parameters
           .map { |p| binding.local_variable_get(p[1]) }

  some_other_method(*params)
  ...do something else...
end

На основе информации, возвращаемой method(__method__).parameters, можно также найти решение, которое будет работать для обоих, но даже с учетом того, что оно будетможно извлечь все это в помощник, это очень сложно.

Ответы [ 3 ]

0 голосов
/ 19 февраля 2019

Просто из любопытства вы можете с помощью Module.prepend:

class Origin
  def m1(p, *args, **kw)
    m2()
  end

  def m2(p, *args, **kw)
    puts "p: #{p}, args: #{args.inspect}, kw: #{kw.inspect}"
  end
end

module Wrapper 
  def m1(*args, **kw)
    @__args__, @__kw__ = args, kw
    super
  end

  def m2(*)
    super(*@__args__, **@__kw__)
  end
end

Origin.prepend(Wrapper)

Origin.new.m1(:foo, 42, bar: :baz)
#⇒ p: foo, args: [42], kw: {:bar=>:baz}
0 голосов
/ 20 февраля 2019

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

Учитывая, что Binding является объектом, как и любой другой, мы можем построить класс для обеспечения функциональности этого типа и использовать привязку из исходного метода для делегирования всех аргументов следующему методу, например, так:

class ArgsBuilder 
  attr_reader
  def initialize(b)
    @standard_args, @kwargs = [],{}
    @binding = b
    build!
  end
  def delegate(m)
    @binding.receiver.send(m,*@standard_args,**@kwargs,&@block)
  end
  private
    def build!
      set_block(&@binding.eval('Proc.new')) if @binding.eval('block_given?')
      @binding.eval('method(__method__)') 
        .parameters.each do |type,name|
          next if type == :block
          val = @binding.local_variable_get(name)
          if type =~ /key/
            @kwargs.merge!(type == :keyrest ? val : {name => val})
          else  
            type == :rest ? @standard_args.concat(val) : @standard_args << val
          end
        end
    end
    def set_block(&block)
      @block = block
    end
end

Использование:

class B 

  def some_method(a,b,c, *d, e:, f:, g: nil, **h)
    ArgsBuilder.new(binding).delegate(:some_other_method)      
  end

  def some_other_method(a,b,c, *d, e:, f:, g: , **h)
     yield __method__ if block_given?
    "I received: #{[a,b,c,d,e,f,g,h].inspect}"
  end
end

B.new.some_method(1,2,3,4,5,6, e: 7, f: 8, p: 9, n: 10) do |m| 
  puts "called from #{m}"
end
# called from some_other_method
#=> "I received: [1, 2, 3, [4, 5, 6], 7, 8, nil, {:p=>9, :n=>10}]"
#                 a  b  c  ----d----  e  f   g   -------h-------

Кажется достаточно простым, чтобы быть помощником и обрабатывать именованные и анонимные блоки, проходящие через методы.

TL; DR

Это, очевидно, требует сопоставления или, по крайней мере, приемлемых подписей в делегированном методе, аналогично тому, как работает super.Однако мы могли бы сделать это довольно далеко и создать классы для типов аргументов [:req,:opt,:rest,:keyreq,:key,:keyrest,:block], поместить их в коллекцию и затем опросить делегируемый метод, чтобы определить правильные аргументы для прохождения;однако я не уверен, что этот тип примера хорошо вписался бы в SO сообщение.

Дополнительное примечание: поскольку в initialize вызывается build!, привязка локальной переменной является статической в ​​момент создания класса ArgsBuilder.например,

 def foo(n) 
   builder = ArgsBuilder.new(binding) 
   n = 17
   builder.delegate(:bar)
 end 
 def bar(n) 
   n 
 end 

  foo(42) 
  #=> 42

Однако

 def foo(n) 
   n = 17
   ArgsBuilder.new(binding).delegate(:bar)
 end 

 foo(42) 
 #=> 17

Это не означает, что изменяемые объекты не могут быть изменены

 def foo(n)
   builder = ArgsBuilder.new(binding) 
   n.upcase!
   builder.delegate(:bar)
 end

 foo('a')
 #=> "A"

Очевидно, вы можете изменить это, просто переместив вызовдо build!

0 голосов
/ 19 февраля 2019

Возможно, вы имеете в виду это

def some_method *args, **kwargs, &block
  ...
  some_other_method(*args, **kwargs, &block)
  ...
end

Но я думаю, что использовать делегирование методов на some _other_method.

будет чище.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...