Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 142 lines (90 sloc) 8.244 kb
6bb1a09 @raganwald
raganwald authored
1 The Enchaining and Obdurate Kestrels
2 ===
3
4 Wherein we look at an interesting way to implement method chaining and meet a new Ruby kestrel.
5
a25e6ef better titles
Reg Braithwaite authored
6 The Enchaining Kestrel
f706640 the obdurate kestrel
Reg Braithwaite authored
7 ---
8
94aa33b @raganwald Updated links, especially to combinators
raganwald authored
9 In [Kestrels](http://github.com/raganwald/homoiconic/tree/master/2008-10-29/kestrel.markdown#readme), we looked at `#tap` from Ruby 1.9 and `returning` from Ruby on Rails. Today we're going to look at another use for `tap`.
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
10
a2ba991 @raganwald Fix some markdown that github doesn't like
raganwald authored
11 [![Kestrel Composite (c) 2007 Mark Kilner](http://farm3.static.flickr.com/2165/1902016010_6f007bf3f0.jpg)](http://flickr.com/photos/markkilner/1902016010/ "Kestrel Composite (c) 2007 Mark Kilner")
f706640 the obdurate kestrel
Reg Braithwaite authored
12
13 As already explained, Ruby 1.9 includes the new method `Object#tap`. It passes the receiver to a block, then returns the receiver no matter what the block contains. The canonical example inserts some logging in the middle of a chain of method invocations:
14
15 address = Person.find(...).tap { |p| logger.log "person #{p} found" }.address
16
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
17 `Object#tap` is also useful when you want to execute several method on the same object without having to create a lot of temporary variables, a practice Martin Fowler calls [Method Chaining](http://martinfowler.com/dslwip/MethodChaining.html ""). Typically, you design such an object so that it returns itself in response to every modifier message. This allows you to write things like:
18
19 HardDrive.new.capacity(150).external.speed(7200)
20
21 Instead of:
22
23 hd = HardDrive.new
24 hd.capacity = 150
25 hd.external = true
26 hd.speed = 7200
27
28 And if you are a real fan of the Kestrel, you would design your class with an object initializer block so you could write:
29
30 hd = HardDrive.new do
31 @capacity = 150
32 @external = true
33 @speed = 7200
34 end
35
36 But what do you do when handed a class that was not designed with method chaining in mind? For example, `Array#pop` returns the object being popped, not the array. Before you validate every criticism leveled against Ruby for allowing programmers to rewrite methods in core classes, consider using `#tap` with `Symbol#to_proc` or `String#to_proc` to chain methods without rewriting them.
37
38 So instead of
f706640 the obdurate kestrel
Reg Braithwaite authored
39
40 def fizz(arr)
41 arr.pop
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
42 arr.map! { |n| n * 2 }
f706640 the obdurate kestrel
Reg Braithwaite authored
43 end
44
45 We can write:
46
47 def fizz(arr)
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
48 arr.tap(&:pop).map! { |n| n * 2 }
f706640 the obdurate kestrel
Reg Braithwaite authored
49 end
50
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
51 I often use `#tap` to enchain methods for those pesky array methods that sometimes do what you expect and sometimes don't. My most hated example is [`Array#uniq!`](http://ruby-doc.org/core/classes/Array.html#M002238 "Class: Array"):
f706640 the obdurate kestrel
Reg Braithwaite authored
52
53 arr = [1,2,3,3,4,5]
54 arr.uniq, arr
55 => [1,2,3,4,5], [1,2,3,3,4,5]
56 arr = [1,2,3,3,4,5]
57 arr.uniq!, arr
58 => [1,2,3,4,5], [1,2,3,4,5]
59 arr = [1,2,3,4,5]
60 arr.uniq, arr
61 => [1,2,3,4,5], [1,2,3,4,5]
62 arr = [1,2,3,4,5]
63 arr.uniq!, arr
64 => nil, [1,2,3,4,5]
65
66 Let's replay that last one in slow motion:
67
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
68 [ 1, 2, 3, 4, 5 ].uniq!
f706640 the obdurate kestrel
Reg Braithwaite authored
69 => nil
70
71 That might be a problem. For example:
72
73 [1,2,3,4,5].uniq!.sort!
74 => NoMethodError: undefined method `sort!' for nil:NilClass
75
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
76 `Object#tap` to the rescue: When using a method like `#uniq!` that modifies the array in place and sometimes returns the modified array but sometimes helpfully returns `nil`, I can use `#tap` to make sure I always get the array, which allows me to enchain methods:
f706640 the obdurate kestrel
Reg Braithwaite authored
77
78 [1,2,3,4,5].tap(&:uniq!).sort!
79 => [1,2,3,4,5]
80
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
81 So there's another use for `#tap` (along with `Symbol#to_proc` for simple cases): We can use it when we want to enchain methods, but the methods do not return the receiver.
82
83 > In Ruby 1.9, `#tap` works exactly as described above. Ruby 1.8 does not have `#tap`, but you can obtain it by installing the andand gem. This version of `#tap` also works like a [quirky bird](http://github.com/raganwald/homoiconic/tree/master/2008-11-04/quirky_birds_and_meta_syntactic_programming.markdown ""), so you can write things like `HardDrive.new.tap.capacity(150)` for enchaining methods that take parameters and/or blocks. To get andand, `sudo gem install andand`. Rails users can also drop [andand.rb](http:andand.rb) in `config/initializers`.
f706640 the obdurate kestrel
Reg Braithwaite authored
84
a25e6ef better titles
Reg Braithwaite authored
85 The Obdurate Kestrel
86 ---
f706640 the obdurate kestrel
Reg Braithwaite authored
87
a2ba991 @raganwald Fix some markdown that github doesn't like
raganwald authored
88 [![Kestrel (c) 2007 The Hounds of Shadow](http://farm3.static.flickr.com/2402/2115973156_f4fcfca811.jpg)](http://flickr.com/photos/thehoundsofshadow/2115973156/ "Kestrel (c) 2007 The Hounds of Shadow")
e6cd4c3 what good is a story about a kestrel without a picture?
Reg Braithwaite authored
89
f706640 the obdurate kestrel
Reg Braithwaite authored
90 The [andand gem](http://github.com/raganwald/andand/tree "raganwald's andand") includes `Object#tap` for Ruby 1.8. It also includes another kestrel called `#dont`. Which does what it says, or rather *doesn't* do what it says.
91
92 :foo.tap { p 'bar' }
93 bar
94 => :foo # printed 'bar' before returning a value!
95
96 :foo.dont { p 'bar' }
97 => :foo # without printing 'bar'!
98
99 `Object#dont` simply ignores the block passed to it. So what is it good for? Well, remember our logging example for `#tap`?
100
101 address = Person.find(...).tap { |p| logger.log "person #{p} found" }.address
102
103 Let's turn the logging off for a moment:
104
105 address = Person.find(...).dont { |p| logger.log "person #{p} found" }.address
106
107 And back on:
108
109 address = Person.find(...).tap { |p| logger.log "person #{p} found" }.address
110
111 I typically use it when doing certain kinds of primitive debugging. And it has another trick up its sleeve:
112
113 arr.dont.sort!
114
115 Look at that, it works with method calls like a quirky bird! So you can use it to `NOOP` methods. Now, you could have done that with `Symbol#to_proc`:
116
117 arr.dont(&:sort!)
118
119 But what about methods that take parameters and blocks?
120
121 JoinBetweenTwoModels.dont.create!(...) do |new_join|
122 # ...
123 end
124
c195ca4 @raganwald
raganwald authored
125 `Object#dont` is the Ruby-semantic equivalent of commenting out a method call, only it can be inserted inside of an existing expression. That's why it's called the *obdurate kestrel*. It refuses to do anything!
f706640 the obdurate kestrel
Reg Braithwaite authored
126
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
127 If you want to try `Object#dont`, or want to use `Object#tap` with Ruby 1.8, `sudo gem install andand`. Rails users can also drop [andand.rb](http:andand.rb) in `config/initializers` as mentioned above. Enjoy!
f706640 the obdurate kestrel
Reg Braithwaite authored
128
fe91187 @raganwald mockingbird links and removing the "NEW!"
raganwald authored
129 _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), [Wrapping Combinators](http://github.com/raganwald/homoiconic/tree/master/2009-06-29/wrapping_combinators.md#readme), and [Mockingbirds and Simple Recursive Combinators in Ruby](https://github.com/raganwald/homoiconic/blob/master/2011/11/mockingbirds.md#readme).
74318fe updated the aviary
Reg Braithwaite authored
130
7d4a695 @raganwald dasherize the mores
raganwald authored
131 ---
f69ce70 @raganwald Link to the book
raganwald authored
132
4479986 @raganwald More recent work!
raganwald authored
133 Recent work:
134
0f5c163 @raganwald added a link to CoffeeScript Ristretto
raganwald authored
135 * [CoffeeScript Ristretto](http://leanpub.com/coffeescript-ristretto), [Kestrels, Quirky Birds, and Hopeless Egocentricity](http://leanpub.com/combinators) and my [other books](http://leanpub.com/u/raganwald).
be2b3de @raganwald updated the footer
raganwald authored
136 * [Cafe au Life](http://recursiveuniver.se), a CoffeeScript implementation of Bill Gosper's HashLife written in the [Williams Style](https://github.com/raganwald/homoiconic/blob/master/2011/11/COMEFROM.md).
3292cde @raganwald better links on each page
raganwald authored
137 * [Katy](http://github.com/raganwald/Katy), a library for writing fluent CoffeeScript and JavaScript using combinators.
0f5c163 @raganwald added a link to CoffeeScript Ristretto
raganwald authored
138 * [Method Combinators](https://github.com/raganwald/method-combinators), a CoffeeScript/JavaScript library for writing method decorators, simply and easily.
f69ce70 @raganwald Link to the book
raganwald authored
139
16fa4bb @raganwald separated .sig
raganwald authored
140 ---
141
7e71700 @raganwald updated links
raganwald authored
142 [Reg Braithwaite](http://braythwayt.com) | [@raganwald](http://twitter.com/raganwald)
Something went wrong with that request. Please try again.