Как реализовать “обратный вызов” в Ruby?

Я просто заметил, что Вы сослались моя статья .

, Спецификация Java говорит, что все в Java - передача значением. Нет такой вещи как "передача ссылкой" в Java.

ключ к пониманию это - то, что что-то как [1 145]

Dog myDog;

не Собака; это на самом деле указатель Собаке.

то, Что это означает, когда Вы имеете

Dog myDog = new Dog("Rover");
foo(myDog);

, Вы чрезвычайно передающие адрес из созданных Dog объект к foo метод.

(я говорю по существу, потому что указатели Java не являются прямыми адресами, но является самым легким думать о них тот путь)

предположим эти Dog, объект находится в адресе памяти 42. Это означает, что мы передаем 42 методу.

, если Метод был определен как [1 151]

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

, позволяют нам посмотреть на то, что происходит.

  • параметр someDog устанавливается на значение 42
  • в строке "AAA"
    • someDog, сопровождается к Dog, это указывает на (эти Dog объект в адресе 42)
    • , что Dog (тот в адресе 42) попросился изменить его имя на Max
  • в строке "BBB"
    • , новое Dog создается. Скажем, он в адресе 74
    • , мы присваиваем параметр someDog 74
  • в строке "CCC"
    • , someDog сопровождается к Dog, это указывает на (эти Dog объект в адресе 74)
    • , что Dog (тот в адресе 74) попросился изменить его имя на Rowlf
  • тогда, мы возвращаемся

Теперь, давайте думать о том, что происходит вне метода:

Сделал myDog изменение?

существует ключ.

Учет того факта, который myDog указатель , и не фактическое Dog, ответ, НЕТ. myDog все еще имеет значение 42; это все еще указывает на оригинал Dog (но обратите внимание, что из-за строки "AAA", его именем является теперь "Max" - все еще та же Собака; myDog значение не изменилось.)

Это совершенно допустимо к [1 131], следуют адрес и изменяют то, что в конце его; это не заменяет переменную, как бы то ни было.

Java работает точно как C. Можно присвоить указатель, передать указатель на метод, следовать за указателем в методе и изменить данные, на которые указали. Однако Вы не можете измениться, где тот указатель указывает.

В C++, Ada, Паскале и других языках, которые поддерживают передачу ссылкой, можно на самом деле заменить переменную, которая была передана.

, Если бы Java имел семантику передачи ссылкой, foo метод, мы определили выше, изменился бы, где myDog указывал, когда это присвоилось someDog на строке BBB.

Думают о параметрах ссылки, как являющихся псевдонимами для переменной, переданной в. Когда тот псевдоним присвоен, так переменная, которая была передана в.

71
задан Justicle 15 May 2012 в 19:09
поделиться

4 ответа

Рубиновым эквивалентом, который не является идиоматическим, будет:

def my_callback(a, b, c, status_code)
  puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}"
end

def do_stuff(a, b, c, callback)
  sum = a + b + c
  callback.call(a, b, c, sum)
end

def main
  a = 1
  b = 2
  c = 3
  do_stuff(a, b, c, method(:my_callback))
end

Идиоматический подход заключается в передаче блока вместо ссылки на метод. Одним из преимуществ блока перед автономным методом является контекст: блок - это замыкание , поэтому он может ссылаться на переменные из области, в которой он был объявлен. Это сокращает количество параметров, которые do_stuff необходимо передать обратному вызову. Например:

def do_stuff(a, b, c, &block)
  sum = a + b + c
  yield sum
end

def main
  a = 1
  b = 2
  c = 3
  do_stuff(a, b, c) { |status_code|
    puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}"
  }
end
85
ответ дан 24 November 2019 в 12:56
поделиться

This "idiomatic block" is a very core part of everyday Ruby and is covered frequently in books and tutorials. The Ruby information section provides links to useful [online] learning resources.


The idiomatic way is to use a block:

def x(z)
  yield z   # perhaps used in conjunction with #block_given?
end
x(3) {|y| y*y}  # => 9

Or perhaps converted to a Proc; here I show that the "block", converted to a Proc implicitly with &block, is just another "callable" value:

def x(z, &block)
  callback = block
  callback.call(z)
end

# look familiar?
x(4) {|y| y * y} # => 16

(Only use the above form to save the block-now-Proc for later use or in other special cases as it adds overhead and syntax noise.)

However, a lambda can be use just as easily (but this is not idiomatic):

def x(z,fn)
  fn.call(z)
end

# just use a lambda (closure)
x(5, lambda {|y| y * y}) # => 25

While the above approaches can all wrap "calling a method" as they create closures, bound Methods can also be treated as first-class callable objects:

class A
  def b(z)
    z*z
  end
end

callable = A.new.method(:b)
callable.call(6) # => 36

# and since it's just a value...
def x(z,fn)
  fn.call(z)
end
x(7, callable) # => 49

In addition, sometimes it's useful to use the #send method (in particular if a method is known by name). Here it saves an intermediate Method object that was created in the last example; Ruby is a message-passing system:

# Using A from previous
def x(z, a):
  a.__send__(:b, z)
end
x(8, A.new) # => 64

Happy coding!

77
ответ дан 24 November 2019 в 12:56
поделиться

Так что, это может быть очень "нерубиново", и я не "профессиональный" Ruby-разработчик, так что если вы, ребята, собираетесь шлепнуть, будьте осторожны, пожалуйста :)

Ruby имеет встроенный модуль под названием Observer. Я не нашел его простым в использовании, но, честно говоря, я не дал ему особого шанса. В своих проектах я прибегал к созданию собственного типа EventHandler (да, я много использую C #). Вот основная структура:

class EventHandler

  def initialize
    @client_map = {}
  end

  def add_listener(id, func)
    (@client_map[id.hash] ||= []) << func
  end

  def remove_listener(id)
    return @client_map.delete(id.hash)
  end

  def alert_listeners(*args)
    @client_map.each_value { |v| v.each { |func| func.call(*args) } }
  end

end

Итак, чтобы использовать это, я выставляю его как член класса, доступный только для чтения:

class Foo

  attr_reader :some_value_changed

  def initialize
    @some_value_changed = EventHandler.new
  end

end

Клиенты класса «Foo» могут подписаться на событие, подобное этому:

foo.some_value_changed.add_listener(self, lambda { some_func })

Я уверен, что это не является идиоматическим Ruby, и я просто переношу свой опыт работы с C # на новый язык, но у меня это сработало.

3
ответ дан 24 November 2019 в 12:56
поделиться

Я часто реализую обратные вызовы в Ruby, как в следующем примере. Это очень удобно в использовании.

class Foo
   # Declare a callback.
   def initialize
     callback( :on_die_cast )
   end

   # Do some stuff.
   # The callback event :on_die_cast is triggered.
   # The variable "die" is passed to the callback block.
   def run
      while( true )
         die = 1 + rand( 6 )
         on_die_cast( die )
         sleep( die )
      end
   end

   # A method to define callback methods.
   # When the latter is called with a block, it's saved into a instance variable.
   # Else a saved code block is executed.
   def callback( *names )
      names.each do |name|
         eval <<-EOF
            @#{name} = false
            def #{name}( *args, &block )
               if( block )
                  @#{name} = block
               elsif( @#{name} )
                  @#{name}.call( *args )
               end
            end
         EOF
      end
   end
end

foo = Foo.new

# What should be done when the callback event is triggered?
foo.on_die_cast do |number|
   puts( number )
end

foo.run
0
ответ дан 24 November 2019 в 12:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: