Определите метод, который является закрытием в Ruby

Я переопределяю метод в объекте в рубине, и мне нужен новый метод, чтобы быть закрытием. Например:

def mess_it_up(o)
  x = "blah blah"

  def o.to_s
    puts x  # Wrong! x doesn't exists here, a method is not a closure
  end
end

Теперь, если я определяю Proc, это - закрытие:

def mess_it_up(o)
  x = "blah blah"

  xp = Proc.new {||
    puts x  # This works
  end

  # but how do I set it to o.to_s.

  def o.to_s
    xp.call  # same problem as before
  end
end

Какие-либо идеи, как сделать это?

Спасибо.

9
задан pupeno 22 March 2010 в 20:38
поделиться

2 ответа

Это работает (проверено в irb):

ПРИМЕЧАНИЕ. Изменяется только str - не все экземпляры String. Читайте ниже, чтобы узнать, почему это работает

another_str = "please don't change me!"
str =         "ha, try to change my to_s! hahaha!"
proc = Proc.new { "take that, Mr. str!" }

singleton_class = class << str; self; end

singleton_class.send(:define_method, :to_s) do
  proc.call
end

puts str.to_s         #=> "take that, Mr. str!"
puts another_str.to_s #=> "please don't change me!"

# What! We called String#define_method, right?

puts String           #=>  String
puts singleton_class  #=>  #<Class:#<String:0x3c788a0>>

# ... nope! singleton_class is *not* String
# Keep reading if you're curious :)

Это работает, потому что вы открываете одноэлементный класс str и определяете там метод. Поскольку это, а также вызов Module # define_method , имеют то, что некоторые называют «плоской областью видимости», вы можете получить доступ к переменным, которые были бы вне области видимости, если бы вы использовали def to_s ; 'что бы ни'; конец .

Вы можете проверить некоторые из этих других «заклинаний метапрограммирования» здесь:

media.pragprog.com/titles/ppmetr/spells.pdf


Почему изменяется только str ?

Потому что у Ruby есть пара интересных уловок в рукаве. В объектной модели Ruby вызов метода приводит к тому, что приемник ищет не только его класс (и его предков), но также его одноэлементный класс (или, как назвал бы его Матц, собственный класс). Этот одноэлементный класс позволяет вам [повторно] определить метод для отдельного объекта. Эти методы называются «одноэлементными методами». В приведенном выше примере мы делаем именно это - определяем одноэлементное имя метода to_s . Функционально он идентичен следующему:

def str.to_s
  ...
end

Единственное отличие состоит в том, что мы можем использовать закрытие при вызове Module # define_method , тогда как def - это ключевое слово, которое приводит к изменению сфера.

Почему не может быть проще?

Что ж, хорошая новость в том, что вы программируете на Ruby, так что не стесняйтесь сходить с ума:

class Object
  def define_method(name, &block)
    singleton = class << self; self; end
    singleton.send(:define_method, name) { |*args| block.call(*args) }
  end
end


str = 'test'
str.define_method(:to_s) { "hello" }
str.define_method(:bark) { "woof!" }
str.define_method(:yell) { "AAAH!" }

puts str.to_s #=> hello
puts str.bark #=> woof!
puts str.yell #=> AAAH!

И, если вам интересно ...

Вы знаете методы класса? Или в некоторых языках мы бы назвали их статическими методами? Что ж, их на самом деле не существует в Ruby. В Ruby методы класса - это просто методы, определенные в одноэлементном классе объекта Class.

Если все это звучит безумно, взгляните на ссылки, которые я предоставил выше. Большая часть возможностей Ruby может быть задействована только в том случае, если вы знаете, как метапрограммировать - и в этом случае вам действительно нужно знать об одноэлементных классах / методах и, в более общем плане, об объектной модели Ruby.

HTH

-Charles

23
ответ дан 4 December 2019 в 07:04
поделиться

Кажется, это работает.

class Foo
  def mess_it_up(o)
    x = "blah blah"

    o.instance_variable_set :@to_s_proc, Proc.new { puts x }
    def o.to_s
      @to_s_proc.call
    end
  end
end

var = Object.new
Foo.new.mess_it_up(var)

var.to_s

Проблема в том, что код в def не оценивается до тех пор, пока он не будет запущен, и в новой области. Таким образом, вы должны сначала сохранить блок в переменной экземпляра на объекте, а затем восстановить его.

И define_method не работает, потому что это метод класса, то есть вам придется вызывать его в классе вашего объекта, передавая этот код ВСЕМ экземплярам этого класса, а не только этому экземпляру. .

0
ответ дан 4 December 2019 в 07:04
поделиться
Другие вопросы по тегам:

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