Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

revised to include the enchaining idea

  • Loading branch information...
commit 7012e6907fd0a43a767cb89da1b85cc73d03cf45 1 parent 5a4ed73
Reg Braithwaite authored
Showing with 36 additions and 13 deletions.
  1. +36 −13 2008-11-12/the_obdurate_kestrel.md
View
49 2008-11-12/the_obdurate_kestrel.md
@@ -1,28 +1,51 @@
-The Obdurate Kestrel
+The Enchaining and Obdurate Kestrels
---
-In [Kestrels](http://github.com/raganwald/homoiconic/tree/master/2008-10-29/kestrel.markdown), we looked at `#tap` from Ruby 1.9. Before we get to an amusing variation of `Object#tap`, here's a brief review of `#tap` along with a trick I've found useful.
+In [Kestrels](http://github.com/raganwald/homoiconic/tree/master/2008-10-29/kestrel.markdown), 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` and an unusual kestrel that is useful specifically because it doesn't actually do anything.
-**using #tap with Symbol#to\_proc**
+**the enchaining kestrel**
+
+[![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")
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:
address = Person.find(...).tap { |p| logger.log "person #{p} found" }.address
-`Object#tap` is also useful when you want to execute several method on the same object but the methods inconveniently return something other than the receiver. This is especially handy when combined either with `Symbol#to_proc` or `String#to_proc`. For example, `Array#pop` returns the object being popped, not the array. So instead of:
+`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:
+
+ HardDrive.new.capacity(150).external.speed(7200)
+
+Instead of:
+
+ hd = HardDrive.new
+ hd.capacity = 150
+ hd.external = true
+ hd.speed = 7200
+
+And if you are a real fan of the Kestrel, you would design your class with an object initializer block so you could write:
+
+ hd = HardDrive.new do
+ @capacity = 150
+ @external = true
+ @speed = 7200
+ end
+
+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.
+
+So instead of
def fizz(arr)
arr.pop
- arr.map { |n| n * 2 }
+ arr.map! { |n| n * 2 }
end
We can write:
def fizz(arr)
- arr.tap(&:pop).map { |n| n * 2 }
+ arr.tap(&:pop).map! { |n| n * 2 }
end
-I often use this in production code with 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"):
+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"):
arr = [1,2,3,3,4,5]
arr.uniq, arr
@@ -39,7 +62,7 @@ I often use this in production code with array methods that sometimes do what yo
Let's replay that last one in slow motion:
- [1,2,3,4,5].uniq!
+ [ 1, 2, 3, 4, 5 ].uniq!
=> nil
That might be a problem. For example:
@@ -47,14 +70,14 @@ That might be a problem. For example:
[1,2,3,4,5].uniq!.sort!
=> NoMethodError: undefined method `sort!' for nil:NilClass
-`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:
+`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:
- [1,2,3,4,5].tap(&:uniq!)
- => [1,2,3,4,5]
[1,2,3,4,5].tap(&:uniq!).sort!
=> [1,2,3,4,5]
-So that's the tip: Use `Object#tap` (along with `Symbol#to_proc` for simple cases) when you want to chain several methods to a receiver, but the methods do not return the receiver.
+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.
+
+> 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`.
**the obdurate kestrel**
@@ -97,7 +120,7 @@ But what about methods that take parameters and blocks?
`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!
-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`. Enjoy!
+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!
_Our aviary so far_: [Kestrels](http://github.com/raganwald/homoiconic/tree/master/2008-10-29/kestrel.markdown), [The Thrush](http://github.com/raganwald/homoiconic/tree/master/2008-10-30/thrush.markdown), [Songs of the Cardinal](http://github.com/raganwald/homoiconic/tree/master/2008-10-31/songs_of_the_cardinal.markdown), [Quirky Birds and Meta-Syntactic Programming](http://github.com/raganwald/homoiconic/tree/master/2008-11-04/quirky_birds_and_meta_syntactic_programming.markdown), [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), and [The Obdurate Kestrel](http://github.com/raganwald/homoiconic/tree/master/2008-11-12/the_obdurate_kestrel.md).
Please sign in to comment.
Something went wrong with that request. Please try again.