contextr 0.1.1 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.