Created
January 7, 2019 00:30
-
-
Save Ferdy89/4da3a6b5dc78fd85fbacdf74807a617c to your computer and use it in GitHub Desktop.
Decorating with metaprogramming
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Decorator | |
attr_reader :klass, :config, :decorator | |
def initialize(klass, config, decorator) | |
@klass = klass | |
@config = config | |
@decorator = decorator | |
end | |
def >(method_name) | |
method = klass.instance_method(method_name) | |
# Only local variables are accessible from within `define_method` | |
decorator = self.decorator | |
config = self.config | |
# Redefine the method wrapping it with the decorator | |
klass.define_method(method_name) do |*args| | |
# `self` here is an instance of the class using the decorator. | |
# Both `method` and `decorator` are unbound and we can now bind them to | |
# this instance. | |
bound_method = method.bind(self) | |
bound_decorator = decorator.bind(self) | |
bound_decorator.call(config, bound_method, args) | |
end | |
end | |
end | |
module Decoratable | |
def decorate(method_name) | |
method = instance_method(method_name) | |
define_method(method_name) do |*config| | |
Decorator.new(self, config, method) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Bar | |
extend Memoizable | |
memoize > | |
def bar | |
puts 'first time!' | |
'bar' | |
end | |
end | |
b = Bar.new # => #<Bar> | |
b.bar # => puts 'first time!' and returns 'bar' | |
b.inspect # => #<Bar @bar="bar"> | |
b.bar # => only returns 'bar' | |
require 'benchmark/ips' | |
class TraditionalBar | |
def bar | |
@bar ||= begin | |
puts 'first time!' | |
'bar' | |
end | |
end | |
end | |
Benchmark.ips do |x| | |
meta = Bar.new | |
traditional = TraditionalBar.new | |
x.report('meta') { meta.bar } | |
x.report('traditional') { traditional.bar } | |
x.compare! | |
end | |
# Warming up -------------------------------------- | |
# meta | |
# 50.895k i/100ms | |
# traditional | |
# 275.834k i/100ms | |
# Calculating ------------------------------------- | |
# meta 590.188k (± 3.7%) i/s - 2.952M in 5.009063s | |
# traditional 8.173M (± 3.8%) i/s - 40.823M in 5.003081s | |
# | |
# Comparison: | |
# traditional: 8173312.4 i/s | |
# meta: 590187.6 i/s - 13.85x slower |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Memoizable | |
extend Decoratable | |
# `config` is not used here, but could be used for configuring the decorator. | |
# Example: | |
# # `config` here would be `[{ times: 5 }]` | |
# retry(times: 5) > | |
decorate def memoize(config, method, method_args) | |
var_name = "@#{method.name}" | |
if instance_variable_defined?(var_name) | |
instance_variable_get(var_name) | |
else | |
result = method.call(*method_args) | |
instance_variable_set(var_name, result) | |
result | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment