1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217

# bad use of metaprogramming 
module MetaTimeDSL
  
  {:second => 1, 
   :minute => 60, 
   :hour => 3600, 
   :day => [24,:hours], 
   :week => [7,:days], 
   :month => [30,:days], 
   :year => [364.25, :days]}.each do |meth, amount|
    define_method "m_#{meth}" do
      amount = amount.is_a?(Array) ? amount[0].send(amount[1]) : amount
      self * amount
    end
    alias_method "m_#{meth}s".intern, "m_#{meth}"
  end
  
end
Numeric.send :include, MetaTimeDSL



# Rewrite
module TimeDSL
  
  def second
    self * 1
  end
  alias_method :seconds, :second
  
  def minute
    self * 60
  end
  alias_method :minutes, :minute
  
  def hour
    self * 3600
  end
  alias_method :hours, :hour
  
  def day
    self * 86400
  end
  alias_method :days, :day
  
  def week
    self * 604800
  end
  alias_method :weeks, :week
  
  def month
    self * 18144000
  end
  alias_method :months, :month
  
  def year
    self * 31471200
  end
  alias_method :years, :year
  
end
Numeric.send :include, TimeDSL

module RefaMetaTimeDSL

  {:second => 1, 
   :minute => 60, 
   :hour => 3600, 
   :day => [24,:hours], 
   :week => [7,:days], 
   :month => [30,:days], 
   :year => [364.25, :days]}.each do |meth, amount|
    self.class_eval <<-RUBY
      def r_#{meth}
        #{amount.is_a?(Array) ? "#{amount[0]}.#{amount[1]}" : "#{amount}"}
      end
      alias_method :r_#{meth}s, :r_#{meth}
    RUBY
  end

end
Numeric.send :include, RefaMetaTimeDSL

module EvalMetaTimeDSL
  def self.included(base)
    base.class_eval do
      [ [:e_second, 1], 
        [:e_minute, 60], 
        [:e_hour, 3600], 
        [:e_day, [24,:e_hours]], 
        [:e_week, [7,:e_days]], 
        [:e_month, [30,:e_days]], 
        [:e_year, [365.25, :e_days]]].each do |meth, amount|
          amount = amount.is_a?(Array) ? amount[0].send(amount[1]) : amount
          eval "def #{meth}; self*#{amount}; end"
          alias_method "#{meth}s", meth
        end
    end
  end
end
Numeric.send :include, EvalMetaTimeDSL

module GoodMetaTimeDSL

  SECOND  = 1
  MINUTE  = SECOND * 60
  HOUR    = MINUTE * 60
  DAY     = HOUR * 24
  WEEK    = DAY * 7
  MONTH   = DAY * 30
  YEAR    = DAY * 364.25
  
  %w[SECOND MINUTE HOUR DAY WEEK MONTH YEAR].each do |const_name|
      meth = const_name.downcase
      class_eval <<-RUBY 
        def g_#{meth} 
          self * #{const_name} 
        end 
        alias g_#{meth}s g_#{meth} 
      RUBY
  end
end
Numeric.send :include, GoodMetaTimeDSL



###############################
#
#
# Benchmarks
require "rubygems"
require "rbench"

TIMES = 100_000

RBench.run(TIMES) do

  format :width => 65

  column :times
  column :bad_meta,  :title => "Bad Neta"
  column :no_meta,  :title => "No Meta"
  column :refa, :title => "Refactored"
  column :eval_meta, :title => 'Refactor 2'
  column :good_meta, :title => "Better Meta"
  
    group("MetaTimeDSL") do
      report "with 360.seconds" do
        bad_meta { 360.m_seconds }
        no_meta { 360.seconds }
        refa { 360.r_seconds }
        eval_meta { 360.e_seconds}
        good_meta { 360.g_seconds }
      end
        
      report "with 360.minutes" do
        bad_meta { 360.m_minutes }
        no_meta { 360.minutes }
        refa { 360.r_minutes }
        eval_meta { 360.e_minutes }
        good_meta { 360.g_minutes }
      end
      
      report "with 360.hours" do
        bad_meta  { 360.m_hours }
        no_meta   { 360.hours }
        refa      { 360.r_hours }
        eval_meta { 360.e_hours }
        good_meta { 360.g_hours }
      end
      
      report "with 360.days" do
        bad_meta  { 360.m_days }
        no_meta   { 360.days }
        refa      { 360.r_days }
        eval_meta { 360.e_days }
        good_meta { 360.g_days }
      end
      report "with 360.weeks" do
        bad_meta  { 360.m_weeks }
        no_meta   { 360.weeks }
        refa      { 360.r_weeks }
        eval_meta { 360.e_weeks }
        good_meta { 360.g_weeks }
      end
      report "with 18.months" do
        bad_meta  { 18.m_months }
        no_meta   { 18.months }
        refa      { 18.r_months }
        eval_meta { 18.e_months }
        good_meta { 18.g_months }
      end
      report "with 7.years" do
        bad_meta  { 7.m_years }
        no_meta   { 7.years }
        refa      { 7.r_years }
        eval_meta { 7.e_years }
        good_meta { 7.g_years }
      end
         
    end
end

#####
#
#                                   | Bad Neta | No Meta | Refactored | Refactor 2 | Better Meta |
#  --MetaTimeDSL----------------------------------------------------------------------------------
#  with 360.seconds         x100000 |    0.143 |   0.046 |      0.033 |      0.047 |       0.048 |
#  with 360.minutes         x100000 |    0.152 |   0.045 |      0.033 |      0.046 |       0.046 |
#  with 360.hours           x100000 |    0.127 |   0.044 |      0.032 |      0.045 |       0.048 |
#  with 360.days            x100000 |    0.144 |   0.047 |      0.070 |      0.045 |       0.048 |
#  with 360.weeks           x100000 |    0.129 |   0.044 |      0.070 |      0.046 |       0.047 |
#  with 18.months           x100000 |    0.143 |   0.045 |      0.071 |      0.045 |       0.048 |
#  with 7.years             x100000 |    0.143 |   0.046 |      0.089 |      0.054 |       0.050 |
#  
####