public
Description: An experiment in publishing code and words about code on a small scale.
Homepage: http://github.com/raganwald/homoiconic/tree/master/homoiconic.markdown
Clone URL: git://github.com/raganwald/homoiconic.git
Click here to lend your support to: homoiconic and make a donation at www.pledgie.com !
homoiconic / 2008-11-07 / from_birds_that_compose_to_method_advice.markdown
02b28c6f » raganwald 2008-11-07 fixed the title's capitaliz... 1 Aspect-Oriented Programming in Ruby using Combinator Birds
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 2 ---
3
dddb5535 » raganwald 2008-11-09 moved the reference link up... 4 In [Combinatory Logic](http://en.wikipedia.org/wiki/Combinatory_logic), the bluebird is one of the most important and fundamental combinators, because the bluebird *composes* two other combinators. Although this is usually discussed as part of [functional programming style](http://weblog.raganwald.com/2007/03/why-why-functional-programming-matters.html "Why Why Functional Programming Matters Matters"), it is just as valuable when writing object-oriented programs. In this post, we will develop an [aspect-oriented programming](http://en.wikipedia.org/wiki/Aspect-oriented_programming "") (or "AOP") module that adds before methods and after methods to Ruby programs, with the implementation inspired by the bluebird.
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 5
94aa33b7 » raganwald 2009-02-02 Updated links, especially t... 6 > As explained in [Kestrels](http://github.com/raganwald/homoiconic/tree/master/2008-10-29/kestrel.markdown#readme), the practice of nicknaming combinators after birds was established in Raymond Smullyan's amazing book [To Mock a Mockingbird](http://www.amazon.com/gp/product/0192801422?ie=UTF8&tag=raganwald001-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0192801422). In this book, Smullyan explains combinatory logic and derives a number of important results by presenting the various combinators as songbirds in a forest. Since the publication of the book more than twenty years ago, the names he gave the birds have become standard nicknames for the various combinators.
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 7
8
9 [![Eastern bluebird (c) 2008 Doug Greenberg, some rights reserved reserved](http://farm3.static.flickr.com/2376/2451392973_cd28956b14_o.jpg)](http://www.flickr.com/photos/dagberg/2451392973/ "Eastern bluebird (c) 2008 Doug Greenberg, some rights reserved")
10
11 The bluebird is written `Bxyz = x(yz)`. In Ruby, we can express the bluebird like this:
12
13 bluebird.call(proc1).call(proc2).call(value)
14 => proc1.call(proc2.call(value))
15
16 If this seems a little arcane, consider a simple Ruby expression `(x * 2) + 1`: This expression *composes* multiplication and addition. Composition is so pervasive in programming languages that it becomes part of the syntax, something we take for granted. We don't have to think about it until someone like Oliver Steele writes a library like [functional javascript](http://osteele.com/sources/javascript/functional/) that introduces a `compose` function, then we have to ask what it does.
17
18 Before we start using bluebirds, let's be clear about something. We wrote that `bluebird.call(proc1).call(proc2).call(value)` is equivalent to `proc1.call(proc2.call(value))`. We want to be very careful that we understand what is special about `proc1.call(proc2.call(value))`. How is it different from `proc1.call(proc2).call(value)`?
19
20 The answer is:
21
22 proc1.call(proc2.call(value))
23 => puts value into proc2, then puts the result of that into proc1
24
25 proc1.call(proc2).call(value)
26 => puts proc2 into proc1, getting a function out, then puts value into the new function
27
94aa33b7 » raganwald 2009-02-02 Updated links, especially t... 28 So with a bluebird you can chain functions together in series, while if you didn't have a bluebird all you could do is write functions that transform other functions. Not that there's anything wrong with that, we used that to great effect with [cardinals](http://github.com/raganwald/homoiconic/tree/master/2008-10-31/songs_of_the_cardinal.markdown#readme) and [quirky birds](http://github.com/raganwald/homoiconic/tree/master/2008-11-04/quirky_birds_and_meta_syntactic_programming.markdown#readme).
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 29
871b8d8e » Reg Braithwaite 2008-11-07 a more focused write-up 30 **giving methods advice**
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 31
327ee68b » raganwald 2008-11-09 sp. 32 We're not actually going to [Greenspun](http://en.wikipedia.org/wiki/Greenspun%27s_Tenth_Rule "Greenspun's Tenth Rule - Wikipedia, the free encyclopedia") an entire aspect-oriented layer on top of Ruby, but we will add a simple feature, we are going to add *before and after methods*. You already know what a normal method is. A before method simply specifies some behaviour you want executed before the method is called, while an after method specifies some behaviour you want executed after the method is called. In AOP, before and after methods are called "advice."
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 33
df1ee4e1 » Reg Braithwaite 2008-11-13 include quote from Avdi 34 > There is an unwritten rule that says every Ruby programmer must, at some point, write his or her own AOP implementation --Avdi Grimm
35
327ee68b » raganwald 2008-11-09 sp. 36 Ruby on Rails programmers are familiar with method advice. If you have ever written any of the following, you were using Rails' built-in aspect-oriented programming support:
b8e2c6a7 » Reg Braithwaite 2008-11-07 mentioned Rails 37
38 after_save
39 validates_each
40 alias_method_chain
41 before_filter
42
43 These and other features of Rails implement method advice, albeit in a very specific way tuned to portions of the Rails framework. We're going to implement method advice in a module that you can use in any of your classes, on any method or methods you choose. We'll start with before methods. Here's the syntax we want:
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 44
45 def something(parameter)
46 # do stuff...
47 end
48
49 before :something do |parameter|
50 # stuff to do BEFORE we do stuff...
51 end
52
53 before :something do |parameter|
54 # stuff to do BEFORE stuff to do BEFORE we do stuff...
55 end
56
871b8d8e » Reg Braithwaite 2008-11-07 a more focused write-up 57 As we can see, the before methods get chained together before the method. To keep this nice and clean, we are going to make them work just like composable functions: whatever our before method's block returns will be passed as a parameter up the chain. We also won't fool around with altering the order of before methods, we'll just take them as they come.
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 58
59 This is really simple, we are composing methods. To compare to the bluebird above, we are writing `before`, then the name of a method, then a function. I'll rewrite it like this:
60
61 bluebird.call(something).call(stuff_to_do_before_we_do_stuff).call(value)
62 => something.call(stuff_to_do_before_we_do_stuff.call(value))
63
dddb5535 » raganwald 2008-11-09 moved the reference link up... 64 Now we can see that this newfangled aspect-oriented programming stuff was figured out nearly a century ago by people like [Alonzo Church](http://en.wikipedia.org/wiki/Alonzo_Church).
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 65
01248cb9 » Reg Braithwaite 2008-11-09 links improved 66 Okay, enough history, let's get started. First, we are not going to write any C, so there is no way to actually force the Ruby VM to call our before methods. So instead, we are going to have to rewrite our method. We'll use a [trick](http://blog.jayfields.com/2006/12/ruby-alias-method-alternative.html "Jay Fields' Thoughts: Ruby: Alias method alternative") I found on Jay Fields' blog:
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 67
871b8d8e » Reg Braithwaite 2008-11-07 a more focused write-up 68 module NaiveBeforeMethods
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 69
70 module ClassMethods
71
72 def before(method_sym, &block)
73 old_method = self.instance_method(method_sym)
74 if old_method.arity == 0
75 define_method(method_sym) do
76 block.call
77 old_method.bind(self).call
78 end
79 else
80 define_method(method_sym) do |*params|
81 old_method.bind(self).call(*block.call(*params))
82 end
83 end
84 end
85
86 end
87
88 def self.included(receiver)
89 receiver.extend ClassMethods
90 end
91
92 end
93
ea7a5b96 » Reg Braithwaite 2008-11-07 some tweaks 94 As you can see, we have a special case for methods with no parameters, and when we have a method with multiple parameters, our before method must answer an array of parameters. And the implementation relies on a "flock of bluebirds:" Our before methods and the underlying base method are composed with each other to define the method that is actually executed at run time.
95
96 Using it is very easy:
97
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 98 class SuperFoo
99
100 def one_parameter(x)
101 x + 1
102 end
103
104 def two_parameters(x, y)
105 x * y
106 end
107
108 end
109
110 class Foo < SuperFoo
111
871b8d8e » Reg Braithwaite 2008-11-07 a more focused write-up 112 include NaiveBeforeMethods
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 113
871b8d8e » Reg Braithwaite 2008-11-07 a more focused write-up 114 before :one_parameter do |x|
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 115 x * 2
116 end
117
871b8d8e » Reg Braithwaite 2008-11-07 a more focused write-up 118 before :two_parameters do |x, y|
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 119 [x + y, x - y]
120 end
121
122 end
123
124 Foo.new.one_parameter(5)
125 => 11
126
127 Foo.new.two_parameters(3,1)
128 => 8
129
871b8d8e » Reg Braithwaite 2008-11-07 a more focused write-up 130 > This could be even more useful if it supported methods with blocks. Adventurous readers may want to combine this code with the tricks in [cardinal.rb](http://github.com/raganwald/homoiconic/tree/master/2008-10-31/cardinal.rb) and see if they can build a version of `before` that supports methods that take blocks.
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 131
132 **the super keyword, perhaps you've heard of it?**
133
134 Of course, Ruby provides a means of 'decorating' methods like this by overriding a method and calling `super` within it. So we might have written:
135
136 class Foo < SuperFoo
137
138 def one_parameter(x)
139 super(x * 2)
140 end
141
142 def two_parameters(x, y)
143 super(x + y, x - y)
144 end
145
146 end
147
046aca16 » Reg Braithwaite 2008-11-09 support advice that ignores... 148 On a trivial example, the two techniques seem equivalent, so why bother with the extra baggage? The answer is that using `super` is a little low level. When you see a method definition in a language like Ruby, you don't know whether you are defining a new method, overriding an existing method with entirely new functionality, or "decorating" a method with before advice. Using advice can be useful when you want to signal exactly what you are trying to accomplish.
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 149
046aca16 » Reg Braithwaite 2008-11-09 support advice that ignores... 150 Another reason to prefer method advice is when you want to share some functionality:
151
152 class LoggingFoo < SuperFoo
153
557f5427 » Reg Braithwaite 2008-11-09 support advice that ignores... 154 def one_parameter(x)
046aca16 » Reg Braithwaite 2008-11-09 support advice that ignores... 155 log_entry
557f5427 » Reg Braithwaite 2008-11-09 support advice that ignores... 156 returning(super) do
157 log_exit
158 end
159 end
046aca16 » Reg Braithwaite 2008-11-09 support advice that ignores... 160
557f5427 » Reg Braithwaite 2008-11-09 support advice that ignores... 161 def two_parameters(x, y)
046aca16 » Reg Braithwaite 2008-11-09 support advice that ignores... 162 log_entry
557f5427 » Reg Braithwaite 2008-11-09 support advice that ignores... 163 returning(super) do
164 log_exit
165 end
166 end
046aca16 » Reg Braithwaite 2008-11-09 support advice that ignores... 167
168 end
169
170 This could be written as:
171
172 class LoggingFoo < SuperFoo
173
174 include NaiveBeforeMethods
175
557f5427 » Reg Braithwaite 2008-11-09 support advice that ignores... 176 before :one_parameter, :two_parameters do # see below
046aca16 » Reg Braithwaite 2008-11-09 support advice that ignores... 177 log_entry
178 end
179
180 after :one_parameter, :two_parameters do
181 log_exit
182 end
183
184 end
185
557f5427 » Reg Braithwaite 2008-11-09 support advice that ignores... 186 This cleanly separates the concern of logging from the mechanism of what the methods actually do
046aca16 » Reg Braithwaite 2008-11-09 support advice that ignores... 187
188 > Although this is not the main benefit, method advice also works with methods defined in modules and the current class, not just superclasses. So in some ways it is even more flexible than Ruby's `super` keyword.
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 189
190 **the queer bird**
191
871b8d8e » Reg Braithwaite 2008-11-07 a more focused write-up 192 That looks handy. But we also want an _after method_, a way to compose methods in the other order. Good news, the queer bird combinator is exactly what we want.
6fcbf929 » raganwald 2008-11-07 c'mon, queer birds need a p... 193
194
195 [![happy pride (c) 2008 penguincakes, some rights reserved reserved](http://farm4.static.flickr.com/3035/2891197379_556f528536.jpg)](http://www.flickr.com/photos/penguincakes/2891197379/ "happy pride (c) 2008 penguincakes, some rights reserved")
196
197
198 Written `Qxyz = y(xz)`, the Ruby equivalent is:
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 199
200 queer_bird.call(something).call(stuff_to_do_after_we_do_stuff).call(value)
201 => stuff_to_do_after_we_do_stuff.call(something.call(value))
202
203 Which is, of course:
204
205 def something(parameter)
206 # do stuff...
207 end
208
046aca16 » Reg Braithwaite 2008-11-09 support advice that ignores... 209 after :something do |return_value|
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 210 # stuff to do AFTER we do stuff...
211 end
212
046aca16 » Reg Braithwaite 2008-11-09 support advice that ignores... 213 The difference between before and after advice is that after advice is consumes and transforms whatever the method returns, while before advice consumes and transforms the parameters to the method.
214
ea7a5b96 » Reg Braithwaite 2008-11-07 some tweaks 215 We _could_ copy, paste and modify our bluebird code for the before methods to create after methods. But before you rush off to implement that, you might want to think about a few interesting "real world" requirements:
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 216
217 1. If you define before and after methods in any order, the final result should be that all of the before methods are run before the main method, then all of the after methods. This is not part of combinatory logic, but it's the standard behaviour people expect from before and after methods.
046aca16 » Reg Braithwaite 2008-11-09 support advice that ignores... 218 2. You should be able to apply the same advice to more than one method, for example by writing `after :foo, :bar do ... end`
219 3. If you declare parameters for before advice, whatever it returns will be used by the next method, just like the example above. If you do not declare parameters for before advice, whatever it returns should be ignored. The same goes for after advice.
220 4. If you override the main method, the before and after methods should still work.
221 5. The blocks provided should execute in the receiver's scope, like method bodies.
ea7a5b96 » Reg Braithwaite 2008-11-07 some tweaks 222
223 One implementation meeting these requirements is here: [before\_and\_after\_advice.rb](http://github.com/raganwald/homoiconic/tree/master/2008-11-07/before_and_after_advice.rb "before_and_after_advice.rb"). Embedded in a lot of extra moving parts, the basic pattern of composing methods is still evident:
224
225 # ...
226 define_method(method_sym) do |*params|
227 composition.after.inject(
228 old_method.bind(self).call(
229 *composition.before.inject(params) do |acc_params, block|
230 self.instance_exec(*acc_params, &block)
231 end
232 )
233 ) do |ret_val, block|
234 self.instance_exec(ret_val, &block)
235 end
236 end
237 # ...
238
239 That is why we looked at supporting just before methods first. If you are comfortable with the [na&iuml;ve implementation of before advice](http://github.com/raganwald/homoiconic/tree/master/2008-11-07/naive_before_advice.rb) discussed above, the mechanism is easy to understand. The complete version is considerably more powerful. As mentioned, it supports before and after advice. It also uses `instance_exec` to evaluate the blocks in the receiver's scope, providing access to private methods and instance variables. And it works properly even when you override the method being advised.
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 240
ea7a5b96 » Reg Braithwaite 2008-11-07 some tweaks 241 Please give it a try and let me know what you think.
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 242
5cf316ea » Reg Braithwaite 2008-11-10 Proc#arity should be markdo... 243 p.s. If the sample code gives an error, it could be [a known bug in Ruby 1.8](http://github.com/raganwald/homoiconic/tree/master/2008-11-09/proc_arity.markdown "Proc#arity"). Try declaring your advice with an empty parameter list, e.g. `do || ... end`.
557f5427 » Reg Braithwaite 2008-11-09 support advice that ignores... 244
94aa33b7 » raganwald 2009-02-02 Updated links, especially t... 245 p.p.s. [A comment on implementing method advice](http://github.com/raganwald/homoiconic/tree/master/2008-11-07/comment_on_implementing_advice.markdown#readme).
ac26a353 » Reg Braithwaite 2008-11-10 a comment on implementing m... 246
0f97bf06 » raganwald 2009-06-29 Wrapping Combinators 247 _More on combinators_: [Kestrels](http://github.com/raganwald/homoiconic/tree/master/2008-10-29/kestrel.markdown#readme), [The Thrush](http://github.com/raganwald/homoiconic/tree/master/2008-10-30/thrush.markdown#readme), [Songs of the Cardinal](http://github.com/raganwald/homoiconic/tree/master/2008-10-31/songs_of_the_cardinal.markdown#readme), [Quirky Birds and Meta-Syntactic Programming](http://github.com/raganwald/homoiconic/tree/master/2008-11-04/quirky_birds_and_meta_syntactic_programming.markdown#readme), [Aspect-Oriented Programming in Ruby using Combinator Birds](http://github.com/raganwald/homoiconic/tree/master/2008-11-07/from_birds_that_compose_to_method_advice.markdown#readme), [The Enchaining and Obdurate Kestrels](http://github.com/raganwald/homoiconic/tree/master/2008-11-12/the_obdurate_kestrel.md#readme), [Finding Joy in Combinators](http://github.com/raganwald/homoiconic/tree/master/2008-11-16/joy.md#readme), [Refactoring Methods with Recursive Combinators](http://github.com/raganwald/homoiconic/tree/master/2008-11-23/recursive_combinators.md#readme), [Practical Recursive Combinators](http://github.com/raganwald/homoiconic/tree/master/2008-11-26/practical_recursive_combinators.md#readme), [The Hopelessly Egocentric Blog Post](http://github.com/raganwald/homoiconic/tree/master/2009-02-02/hopeless_egocentricity.md#readme), and [Wrapping Combinators](http://github.com/raganwald/homoiconic/tree/master/2009-06-29/wrapping_combinators.md#readme).
1ed0f936 » Reg Braithwaite 2008-11-07 New post: From birds that c... 248
249 ---
d0d3f5b0 » Reg Braithwaite 2008-11-07 updated the footer 250
f0aad1ee » Reg Braithwaite 2008-12-02 and simplified the footers ... 251 Subscribe to [new posts and daily links](http://feeds.feedburner.com/raganwald "raganwald's rss feed"): <a href="http://feeds.feedburner.com/raganwald"><img src="http://feeds.feedburner.com/~fc/raganwald?bg=&amp;fg=&amp;anim=" height="26" width="88" style="border:0" alt="" align="top"/></a>
d0d3f5b0 » Reg Braithwaite 2008-11-07 updated the footer 252
a4bfc714 » raganwald 2009-09-23 updated resume link 253 Reg Braithwaite: [Home Page](http://reginald.braythwayt.com), [CV](http://reginald.braythwayt.com/RegBraithwaiteGH0909_en_US.pdf ""), [Twitter](http://twitter.com/raganwald)