Я переопределяю метод в объекте в рубине, и мне нужен новый метод, чтобы быть закрытием. Например:
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
Какие-либо идеи, как сделать это?
Спасибо.
Это работает (проверено в 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
Кажется, это работает.
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
не работает, потому что это метод класса, то есть вам придется вызывать его в классе вашего объекта, передавая этот код ВСЕМ экземплярам этого класса, а не только этому экземпляру. .