Это работает (проверено в 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 :)
Это работает, потому что вы открываете singleton класс str и определяете там метод. Поскольку это так же, как и вызов Module # define_method , имеет то, что некоторые называют «плоской областью видимости», вы можете получить доступ к переменным, которые были бы вне области, если бы вы использовали def to_s; 'whatever'; end
.
Вы можете проверить некоторые из этих «заклинаний метапрограммирования» здесь:
media.pragprog.com / названия / ppmetr / spells.pdf
Почему меняется только str
?
Потому что у Руби есть пара интересных трюков, это рукава. В объектной модели Ruby, вызов метода приводит к тому, что получатель ищет не только свой класс (и это предки), но также и его синглтон-класс (или, как сказал бы Matz, это собственный класс). Этот одноэлементный класс - это то, что позволяет вам [re] определять метод для одного объекта. Эти методы называются «одноэлементными методами». В приведенном выше примере мы делаем именно это - определяем одноэлементное имя метода 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.
НТН
-Charles