Определите метод, который является замыканием в Ruby - PullRequest
9 голосов
/ 22 марта 2010

Я переопределяю метод в объекте в 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

Есть идеи как это сделать?

Спасибо.

Ответы [ 3 ]

23 голосов
/ 22 марта 2010

Это работает (проверено в 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

9 голосов
/ 14 февраля 2011

Функция # 1082 , реализованная в Ruby 1.9.2, делает эту задачу легкой с Object # define_singleton_method :

def mess_it_up(o)
  x = "blah blah"

  # Use Object#define_singleton_method to redefine `to_s'
  o.define_singleton_method(:to_s) { x }
end

Используемые концепции все те же, что и в моем предыдущем ответе , в котором дается более подробное описание того, как это работает в объектной модели Ruby, а также определение Object#define_method, концептуально такой же как в Ruby 1.9.2 Object#define_singleton_method.

Другие методы, которые могут оказаться полезными для подобных задач:

0 голосов
/ 22 марта 2010

Это похоже на работу.

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 не работает, потому что это метод класса, то есть вам придется вызывать его для класса вашего объекта, передавая этот код ВСЕМ экземплярам этого класса, а не только этому экземпляру.

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