Как я “фальсифицирую” атрибуты стиля C# в Ruby?

Существует, в последнем стандарте, ключевое слово (export), которое помогло бы облегчить эту проблему, но это не реализовано ни в каком компиляторе, о котором я знаю кроме Comeau.

Посмотрите облегченный FAQ об этом.

5
задан Sam Saffron 6 July 2009 в 02:03
поделиться

2 ответа

У меня несколько иной подход:

class Object
  def self.profile(method_name)
    return_value = nil
    time = Benchmark.measure do
      return_value = yield
    end

    puts "#{method_name} finished in #{time.real}"
    return_value
  end
end

require "benchmark"

module Profiler
  def method_added(name)
    profile_method(name) if @method_profiled
    super
  end

  def profile_method(method_name)
    @method_profiled = nil
    alias_method "unprofiled_#{method_name}", method_name
    class_eval <<-ruby_eval
      def #{method_name}(*args, &blk)
        name = "\#{self.class}##{method_name}"
        msg = "\#{name} was called with \#{args.inspect}"
        msg << " and a block" if block_given?
        puts msg

        Object.profile(name) { unprofiled_#{method_name}(*args, &blk) }
      end
    ruby_eval
  end

  def profile
    @method_profiled = true
  end
end

module Comment
  def method_added(name)
    comment_method(name) if @method_commented
    super
  end

  def comment_method(method_name)
    comment = @method_commented
    @method_commented = nil
    alias_method "uncommented_#{method_name}", method_name
    class_eval <<-ruby_eval
      def #{method_name}(*args, &blk)
        puts #{comment.inspect}
        uncommented_#{method_name}(*args, &blk)
      end
    ruby_eval
  end

  def comment(text)
    @method_commented = text
  end
end

class Foo
  extend Profiler
  extend Comment

  # This is the fake attribute, it profiles a single method.
  profile
  def bar(b)
   puts b
  end

  def barbar(b)
    puts(b)
  end

  comment("this really should be fixed")
  def snafu(b)
  end
end

Несколько замечаний по поводу этого решения:

  • Я предоставил дополнительные методы через модули, которые при необходимости могут быть расширены в новые классы. Это позволяет избежать загрязнения глобального пространства имен для всех модулей.
  • Я избегал использования alias_method, поскольку модуль включает разрешающие расширения в стиле AOP (в данном случае для method_added ) без необходимости использования псевдонимов.
  • I решил использовать class_eval вместо define_method для определения нового метода, чтобы иметь возможность поддерживать методы, которые принимают блоки. Это также потребовало использования alias_method .
  • Поскольку я выбрал поддержку блоков, я также добавил немного текста в вывод на случай, если метод принимает блок.
  • Есть способы получить фактические имена параметров, что было бы ближе к исходному результату, но здесь они не подходят для ответа. Вы можете проверить merb-action-args , где мы написали код, который требовал получения фактических имен параметров. Он работает в JRuby, Ruby 1.8.x, Ruby 1.9.1 (с гемом) и Ruby 1.9 trunk (изначально).
  • Основной метод здесь - сохранить переменную экземпляра класса, когда профиль или вызывается комментарий , который затем применяется при добавлении метода. Как и в предыдущем решении, ловушка method_added используется для отслеживания добавления нового метода, но вместо того, чтобы каждый раз удалять ловушку, ловушка проверяет наличие переменной экземпляра. Переменная экземпляра удаляется после применения АОП, поэтому применяется только один раз. Если этот же метод использовался несколько раз, его можно было бы далее абстрагировать.
  • В общем, я старался придерживаться вашей «спецификации», насколько это возможно, поэтому я включил фрагмент Object.profile вместо того, чтобы реализовывать его встроенным.
11
ответ дан 18 December 2019 в 09:09
поделиться

Отличный вопрос. Это моя быстрая попытка реализации (я не пытался оптимизировать код). Я взял на себя смелость добавить метод профиля в Модуль класс. Таким образом, он будет доступен в каждом определении класса и модуля. Было бы даже лучше чтобы извлечь его в модуль и смешать с классом Module всякий раз, когда он вам понадобится.

Я также не знал, был ли смысл в том, чтобы заставить метод профиля вести себя как общедоступные / защищенные / частные ключевые слова Ruby, но я все равно так реализовал. Все методы, определенные после вызова profile , профилируются до тех пор, пока не будет вызван noprofile .

class Module
  def profile
    require "benchmark"
    @profiled_methods ||= []
    class << self
      # Save any original method_added callback.
      alias_method :__unprofiling_method_added, :method_added
      # Create new callback.
      def method_added(method)
        # Possible infinite loop if we do not check if we already replaced this method.
        unless @profiled_methods.include?(method)
          @profiled_methods << method
          unbound_method = instance_method(method)
          define_method(method) do |*args|
            puts "#{self.class}##{method} was called with params #{args.join(", ")}"
            bench = Benchmark.measure do
              unbound_method.bind(self).call(*args)
            end
            puts "#{self.class}##{method} finished in %.5fs" % bench.real
          end
          # Call the original callback too.
          __unprofiling_method_added(method)
        end
      end
    end
  end

  def noprofile # What's the opposite of profile?
    class << self
      # Remove profiling callback and restore previous one.
      alias_method :method_added, :__unprofiling_method_added
    end
  end
end

Теперь вы можете использовать его следующим образом:

class Foo
  def self.method_added(method) # This still works.
    puts "Method '#{method}' has been added to '#{self}'."
  end

  profile

  def foo(arg1, arg2, arg3 = nil)
    puts "> body of foo"
    sleep 1
  end

  def bar(arg)
    puts "> body of bar"
  end

  noprofile

  def baz(arg)
    puts "> body of baz"
  end
end

Вызовите методы, как обычно:

foo = Foo.new
foo.foo(1, 2, 3)
foo.bar(2)
foo.baz(3)

И получить результаты тестирования (и результат исходного method_added обратного вызова, чтобы показать, что он все еще работает):

Method 'foo' has been added to 'Foo'.
Method 'bar' has been added to 'Foo'.
Method 'baz' has been added to 'Foo'.
Foo#foo was called with params 1, 2, 3
> body of foo
Foo#foo finished in 1.00018s
Foo#bar was called with params 2
> body of bar
Foo#bar finished in 0.00016s
> body of baz

Следует отметить, что невозможно динамически получить имя аргументов с помощью Мета-программирование на Ruby. Вам нужно будет проанализировать исходный файл Ruby, что, безусловно, возможно, но немного сложнее. См. parse_tree и ruby_parser драгоценные камни для деталей.

Забавным улучшением была бы возможность определять такое поведение с помощью метода класса в классе Module . Было бы здорово иметь возможность сделать что-нибудь вроде:

class Module
  method_wrapper :profile do |*arguments|
    # Do something before calling method.
    yield *arguments  # Call original method.
    # Do something afterwards.
  end
end

Я оставлю это упражнение по мета-мета-программированию на другой раз. : -)

7
ответ дан 18 December 2019 в 09:09
поделиться
Другие вопросы по тегам:

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