contextr 0.1.1 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,311 +1,4 @@
1
1
  require File.dirname(__FILE__) + "/test_helper.rb"
2
2
 
3
3
  test_class(:TestIntroduction)
4
-
5
- # Let's build a simple student's database.
6
- # Each University has a name and address. Each student has a name, address
7
- # and an associated university.
8
- #
9
- # We are using a Struct to build our classes in an easy way. This provides all
10
- # getters, setters and an easy constructor setting all the instance variables.
11
- #
12
- # In order to get a nice output in our program we override the #to_s method
13
- # which is used in many cases by ruby, e.g. in Kernel#puts or in String
14
- # interpolation.
15
- #
16
- # In most of the cases, the name is sufficient to represent each entity, i.e.
17
- # a student or a university.
18
-
19
- class University < Struct.new(:name, :address)
20
- def to_s
21
- name
22
- end
23
- end
24
-
25
- class Student < Struct.new(:name, :address, :university)
26
- def to_s
27
- name
28
- end
29
- end
30
-
31
- # Under certain circumstances we would like to have a more verbose output.
32
- # This could mean print the university a student belongs to or attach the
33
- # address to the output.
34
-
35
-
36
- # Additonal methods
37
- # =================
38
-
39
- # In a plain old Ruby project, this would result in additional methods, propably
40
- # encapsulated in modules, that will be included into our classes. This
41
- # allows reuse and better encapsulation.
42
-
43
- module AddressOutput
44
- def to_s_with_address
45
- "#{self} (#{self.address})"
46
- end
47
- end
48
-
49
- class University
50
- include AddressOutput
51
- end
52
-
53
- # Now each university got a to_s_with_address method that could be called
54
- # instead of to_s if you would like to have additional information.
55
-
56
- class Student
57
- include AddressOutput
58
-
59
- def to_s_with_university
60
- "#{self}; #{self.unversity}"
61
- end
62
- def to_s_with_university_and_address
63
- "#{self.to_s_with_address}; #{self.unversity.to_s_with_address}"
64
- end
65
- end
66
-
67
- # The same for each student. #to_s_with_unversity and
68
- # #to_s_with_university_and_address give as well additional output.
69
- #
70
- # So how can you use it. Let's create some instances first.
71
-
72
- $hpi = University.new("HPI", "Potsdam")
73
- $gregor = Student.new("Gregor", "Berlin", $hpi)
74
-
75
- # An now some output. This could live inside an erb template, a graphical ui or
76
- # printed to the command line. In all these cases to_s is called automatically
77
- # by the standard libary to receive a good representation of the object.
78
- # The output method defined in test_helper.rb simulates this behaviour. All
79
- # examples are converted to test class automatically, so we can be sure, that
80
- # this document stays in sync with the libary.
81
- #
82
- # puts gregor # => prints "Gregor"
83
- # "#{gregor}" # => evaluates to "Gregor"
84
- # <%= gregor %> => as well as this
85
-
86
- example do
87
- output_of($gregor) == "Gregor"
88
- output_of($hpi) == "HPI"
89
- end
90
-
91
- # Assume, we would like to print an address list now.
92
-
93
- example do
94
- output_of($gregor.to_s_with_address) == "Gregor (Berlin)"
95
- end
96
-
97
- # If you want a list with university and addresses, you would use
98
- # #to_s_with_university_and_address. No automatic call to to_s anymore. If you
99
- # have your layout in an erb template, you have to change each and every
100
- # occurrence of your variables.
101
-
102
-
103
- # Redefining to_s
104
- # ===============
105
-
106
- # To solve this problem you could redefine to_s on demand. I will demonstrate
107
- # this with some meta programming in a fresh class.
108
-
109
- module GenericToS
110
- def to_s
111
- self.class.included_vars.collect do |var|
112
- self.send(var)
113
- end.join("; ")
114
- end
115
-
116
-
117
- module ClassMethods
118
- attr_accessor :included_vars
119
- def set_to_s(*included_vars)
120
- self.included_vars = included_vars
121
- end
122
- end
123
-
124
- def self.included(base_class)
125
- base_class.send(:extend, ClassMethods)
126
- end
127
- end
128
-
129
- class Company < Struct.new(:name, :address)
130
- include GenericToS
131
- end
132
-
133
- class Employee < Struct.new(:name, :address, :company)
134
- include GenericToS
135
- end
136
-
137
- # I will not go into detail how this code works, but I will show you how to use
138
- # it. Let's get some instances first.
139
-
140
- $ms = Company.new("Microsoft", "Redmond")
141
- $bill = Employee.new("Bill", "Redmond", $ms)
142
-
143
- # And now use these instances.
144
-
145
- example do
146
- Company.set_to_s(:name)
147
- Employee.set_to_s(:name)
148
-
149
- output_of($ms) == "Microsoft"
150
- output_of($bill) == "Bill"
151
- end
152
-
153
- # Let's get the output including the addresses
154
- example do
155
- Employee.set_to_s(:name, :address)
156
-
157
- output_of($bill) == "Bill; Redmond"
158
- end
159
-
160
- # And including the employer
161
- example do
162
- Employee.set_to_s(:name, :address, :company)
163
-
164
- output_of($bill) == "Bill; Redmond; Microsoft"
165
- end
166
-
167
- # But hey. I wanted to have a list with all addresses, not just to employee's.
168
- # This should be an address list, right? But we did not tell the Company class
169
- # to print the address, but just the Employee class.
170
- #
171
- # So in our first approach, we had to change each place, where we use the
172
- # object. In the second approach we have to know all places where an address
173
- # is stored and apply the changes in there.
174
- #
175
- # By the way, what happens, if i was useing a multi-threaded application and
176
- # one user request a simple name list, and the other switches to an address list
177
- # in the meantime. Then the output will be mixed - with and without addresses.
178
- # This is not exactly what we want. So there has to be an easier, thread safe
179
- # solution.
180
-
181
-
182
- # ContextR
183
- # ========
184
-
185
- # This is were context-oriented programming comes into play. I will again start
186
- # from the scratch. It is not much and we all know the problem space now.
187
-
188
- # The same setup, just another setting. First the basic implementation, just
189
- # like we did it in our first approach
190
- class Religion < Struct.new(:name, :origin)
191
- def to_s
192
- name
193
- end
194
- end
195
- class Believer < Struct.new(:name, :origin, :religion)
196
- def to_s
197
- name
198
- end
199
- end
200
-
201
- # Now define the additional behaviour in separate modules. Please don't be
202
- # scared because of the strange syntax and method calls.
203
- # yield(:receiver) refers to the "normal" self when these modules are included
204
- # yield(:next) is much like a super call.
205
- #
206
- # Future versions of ContextR will hopefully provide a nicer syntax here.
207
- module OriginMethods
208
- def to_s
209
- "#{yield(:next)} (#{yield(:receiver).origin})"
210
- end
211
- end
212
- module ReligionMethods
213
- def to_s
214
- "#{yield(:next)}; #{yield(:receiver).religion}"
215
- end
216
- end
217
-
218
- # Finally we need to link our additional behaviour to our basic classes.
219
- # We also need to tell the framework, when this behaviour should be applied.
220
- class Religion
221
- include OriginMethods => :location
222
- end
223
- class Believer
224
- include OriginMethods => :location
225
- include ReligionMethods => :believe
226
- end
227
-
228
- # The additional context dependent behaviour is organised within layers. A
229
- # single layer may span multiple classes - in this case the location layer does.
230
- # To enable the additional code, the programmes shall activate layers. A
231
- # layer activation is only effective within a block scope and within the
232
- # current thread.
233
- #
234
- # Let's see, how it looks like when we use it.
235
-
236
- $christianity = Religion.new("Christianity", "Israel")
237
- $the_pope = Believer.new("Benedikt XVI", "Bavaria", $christianity)
238
-
239
- example do
240
- output_of($christianity) == "Christianity"
241
- output_of($the_pope) == "Benedikt XVI"
242
- end
243
-
244
- # Would like to have an address? For this we have to activate the location
245
- # layer. Now the additional behaviour defined within the layer, will be
246
- # executed around the base method defined within the class
247
-
248
- example do
249
- ContextR.with_layer :location do
250
- output_of($christianity) == "Christianity (Israel)"
251
- output_of($the_pope) == "Benedikt XVI (Bavaria)"
252
- end
253
- end
254
-
255
- # Of course the additional behaviour is deactivated automatically after the
256
- # blocks execution.
257
-
258
- example do
259
- output_of($christianity) == "Christianity"
260
- output_of($the_pope) == "Benedikt XVI"
261
- end
262
-
263
- # Everything back to normal.
264
- #
265
- # Lets activate the believe layer
266
-
267
- example do
268
- ContextR.with_layer :believe do
269
- output_of($the_pope) == "Benedikt XVI; Christianity"
270
- end
271
- end
272
-
273
- # Now we need both, location and believe. How does it look like? You have to
274
- # options. You may activate the two one after the other or all at once. It
275
- # is just a matter of taste, the result remains the same
276
-
277
- example do
278
- ContextR.with_layer :believe, :location do
279
- output_of($the_pope) == "Benedikt XVI (Bavaria); Christianity (Israel)"
280
- end
281
- end
282
-
283
- # As you can see, the activation of the location layer is operative in the
284
- # whole execution context of the block. Each religion prints its origin, wheter
285
- # to_s was called directly or indirectly.
286
- #
287
- # If you change your mind within your call stack, you may of course deactivate
288
- # layers again.
289
-
290
- example do
291
- ContextR.with_layer :believe do
292
- ContextR::with_layer :location do
293
- output_of($the_pope) == "Benedikt XVI (Bavaria); Christianity (Israel)"
294
-
295
- ContextR.without_layer :believe do
296
- output_of($the_pope) == "Benedikt XVI (Bavaria)"
297
- end
298
-
299
- output_of($the_pope) == "Benedikt XVI (Bavaria); Christianity (Israel)"
300
- end
301
- end
302
- end
303
-
304
- # These encaspulations may be as complex as your application. ContextR will
305
- # keep track of all activations and deactivations within the blocks and
306
- # restore the settings after the block was executed.
307
- #
308
- # This was just a short introduction on a problem case, that can be solved
309
- # with context-oriented programming. You have seen, the advantages and how
310
- # to use it. In other files in this folder, you can learn more on the dynamics
311
- # and meta programming interfaces of ContextR
4
+ LiterateMarukuTest.load(__FILE__)
@@ -0,0 +1,170 @@
1
+ One of the most frequent examples for the use of aspect oriented programming
2
+ is caching. You have a method, that takes some time to compute a result,
3
+ but the result is always the same, if the parameters are the same. Such
4
+ methods are often called functions. The have no side effects and do not depend
5
+ on inner state.
6
+
7
+ Perhaps you would like to attach caching to such functions. But only under
8
+ certain circumstances. If your application lacks time, it is a good idea to
9
+ activate result caching. If it lacks memory, it is a bad one.
10
+
11
+ This perfectly sounds like a problem, that can be solved using
12
+ context-oriented programming.
13
+
14
+ But for caching, we need a place to store the computed results. One could
15
+ store them in the instance that computes them, but this would be strange,
16
+ because, the code residing in it does not make direct use of this state.
17
+ Perhaps we are using variables, that other methods use for their own
18
+ purpose and this would result in strange results. We could use cryptic
19
+ instance variable names, but all this is just a workaround.
20
+
21
+ What we want is to attach the state to the code, that uses it. This is the
22
+ main idea of object-oriented design anyway. So ContextR gives you the
23
+ opportunity to save state inside your method modules. Code and state stay
24
+ side by side. This state will reside there even after deactivating the
25
+ layer, so you can reuse it. Perfect for our caching approach.
26
+
27
+
28
+ Fibonacci numbers
29
+ -----------------
30
+
31
+ We will use the good old Fibonacci numbers to demonstrate our approach. First
32
+ the simple recursive computation, that becomes really slow for larger numbers.
33
+ I know that there is a non-recursive algorithm, working faster, but this
34
+ would not make such a good example. So just assume, that the following
35
+ code is the fastest, you could possibly get.
36
+
37
+ module Fibonacci
38
+ module ClassMethods
39
+ def compute(fixnum)
40
+ if fixnum == 1 or fixnum == 0
41
+ fixnum
42
+ elsif fixnum < 0
43
+ raise ArgumentError, "Fibonacci not defined for negative numbers"
44
+ else
45
+ compute(fixnum - 1) + compute(fixnum - 2)
46
+ end
47
+ end
48
+ end
49
+ self.extend(ClassMethods)
50
+ end
51
+
52
+ example do
53
+ result_of(Fibonacci.compute(1)) == 1
54
+ result_of(Fibonacci.compute(2)) == 1
55
+ result_of(Fibonacci.compute(3)) == 2
56
+ end
57
+
58
+ Just to make sure, that it is slow, I will try to compute Fib(100)
59
+
60
+ require 'timeout'
61
+ example do
62
+ timeout_raised = false
63
+ begin
64
+ Timeout::timeout(0.05) do
65
+ Fibonacci.compute(100)
66
+ end
67
+
68
+ rescue Timeout::Error
69
+ timeout_raised = true
70
+ end
71
+
72
+ result_of(timeout_raised) == true
73
+ end
74
+
75
+ Okay, the 0.01 seconds are really impatient, but I know, that caching will
76
+ come to rescue and makes it happen.
77
+
78
+ Let's define a simple caching method. If I already know the result, return
79
+ it, if not, let the base implementation compute it and save the it into
80
+ our variable.
81
+
82
+ module Fibonacci
83
+ module ClassMethods
84
+ in_layer :cache do
85
+ def cache
86
+ @cache ||= {}
87
+ end
88
+
89
+ def compute(fixnum)
90
+ cache[fixnum] ||= yield(:next, fixnum)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ If you are not familiar with the above syntax, to define context dependent
97
+ behaviour, have a look at `test_class_side.rb`.
98
+
99
+ Now let's compute Fib(100) again. Of course with caching enabled
100
+
101
+ example do
102
+ timeout_raised = false
103
+ begin
104
+ Timeout::timeout(0.05) do
105
+ ContextR::with_layer :cache do
106
+ result_of(Fibonacci.compute(100)) == 354_224_848_179_261_915_075
107
+ end
108
+ end
109
+
110
+ rescue Timeout::Error
111
+ timeout_raised = true
112
+ end
113
+
114
+ # This time the time out was not triggered
115
+ result_of(timeout_raised) == false
116
+ end
117
+
118
+ It is that simple to add state to your method modules. And just to make sure,
119
+ that I did not cheat, I will add a simple case, were instance variables and
120
+ layer specific variables _would_ conflict, but in fact don't.
121
+
122
+ class LayerStateExample
123
+ attr_accessor :state
124
+ in_layer :test_layer_state do
125
+ attr_accessor :state
126
+ end
127
+ end
128
+
129
+ When StateMethods would be included normally, its `attr_accessor` would simply
130
+ be the same as in the class. But this does not happen, when using layers.
131
+
132
+ Let's do a little warm up and make sure, everything works as expected.
133
+
134
+ $layer_state_example = LayerStateExample.new
135
+
136
+ example do
137
+ $layer_state_example.state = true
138
+ result_of($layer_state_example.state) == true
139
+ $layer_state_example.state = false
140
+ result_of($layer_state_example.state) == false
141
+
142
+ ContextR::with_layer :test_layer_state do
143
+ $layer_state_example.state = true
144
+ result_of($layer_state_example.state) == true
145
+ $layer_state_example.state = false
146
+ result_of($layer_state_example.state) == false
147
+ end
148
+ end
149
+
150
+ Until now, I did not prove anything. Let's try it.
151
+
152
+ example do
153
+ # Set the state
154
+ $layer_state_example.state = true
155
+ ContextR::with_layer :test_layer_state do
156
+ $layer_state_example.state = false
157
+ end
158
+
159
+ # And make sure, that they differ
160
+ result_of($layer_state_example.state) == true
161
+
162
+ ContextR::with_layer :test_layer_state do
163
+ result_of($layer_state_example.state) == false
164
+ end
165
+ end
166
+
167
+ The last example was very theoretical and looks strange when seen isolated.
168
+ Its main purpose is to show, that layer specific methods and base methods
169
+ do not share their state, and that the layer specific state remains, also
170
+ after layer deactivation. Don't take too serious.