Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
100644 167 lines (116 sloc) 11.035 kb
0f97bf0 Reg Braithwaite Wrapping Combinators
raganwald authored
1 Wrapping Combinators
2 ===
4 I received a nice email which asked, in essence:
6 > Which combinator says "For every time you do A, remember to do C after you've done B?" Which combinator codifies the problems like freeing memory (C) after using it (B) whenever we have allocated it (A), or stuff like opening files and sockets (A), reading and/or writing from/to them (B), then closing them (C). Or accessing mutexen (A), working therein (B), and leaving (C)?
8 This is an interesting question. In essence, we are looking for a combinator that takes a function and imposes some side effects before and after the function.
10 **The purely combinatorial answer**
12 We want a combinator, "?" where:
14 ?xyz = y
16 Meaning, "compute x, then y, then z, and return the value of y." Now right away the "canonical" list of combinators does not include any such thing. However, there is the rule of *composition*, meaning that for any two combinators A and B, there exists a combinator C such that:
18 Cx = A(Bx)
20 The rule of composition defines the operation of chaining functions together, where the output of one function is fed as the input to another. So what we are seeking is the appropriate composition of existing combinators that will produce the result we seek.
22 Let's look at our desired combinator again:
24 ?xyz = y
26 We've already seen something a little like this: A [Kestrel]( "Kestrels") is a combinator that looks like this:
28 Kxy = x
30 The Kestrel takes a value, "x," and another value, "y," and returns "x" while ignoring "y." In a language with side-effects, and pass-by-value, the Kestrel expresses the idea of computing a value, "x" for the result and then performing some computation, "y" strictly for side-effects after computing "x."
32 This seems useful: It's half of what we want. Let's rewrite it slightly:
34 Kyz = y
36 This is the latter half of what we want. The first half will look something like this, given a combinator "\_":
38 _xyz = yz
40 And then we would be able to compose a Kestrel with "\_":
42 ?xyz = K(_xyz) = y
44 So what is "\_"? We have already decided:
46 _xyz = yz
48 Which is the same as:
50 _xy = y
0d503c3 Reg Braithwaite updated a link to The Thrush
raganwald authored
52 There are a couple of ways to derive this combinator. One way is to compose a [Thrush]( with a Kestrel:
0f97bf0 Reg Braithwaite Wrapping Combinators
raganwald authored
54 Txy = yx, therefore:
55 K(Txy) = Kyx = y
57 (Another easy derivation uses a Kestrel and an Identity to produce the same result.) But let's verify that our composition of Kestrel and Thrush gets the job done:
59 K(Txy) = Kyx = y
60 K(Txyz) = Kyxz = yz
61 K(K(Txyz)) = K(Kyxz) = Kyz = y
63 So composing two Kestrels and a Thrush gives us a combinator that computes three values "x," "y," and "z" and returns "y" while throwing away the results of computing "x" and "z." In a world where side effects matter, this allows us to sandwich the computation of "y" in between "x" and "z."
65 **Ruby**
67 This is a fairly common pattern in Ruby, thanks to the convenience of blocks and the **yield** keyword. It looks a little different than the combinatorial version, but the most popular expression of this idea is probably the syntax for invoking a database transaction in Ruby on Rails:
69 Campaign.transaction do
70 campaign = Campaign.create!(:created_by => current_user)
71 campaign.versions.init! params, current_user
72 redirect_to presentation_path(:id =>, :version => 1)
73 end
75 The #transaction method starts a database transaction using the connection owned by Campaign, yields to the block to execute the code in the block, and if all is successful it will commit the transaction. The results of starting and committing the transaction are discarded, and the #transaction method returns whatever the block returns. This is just like our K(K(Txyz))) combinator above.
77 (The source is obviously a little more complicated because it needs to roll the transaction back if there is an exception thrown, but the basic principle applies.)
79 While the syntax of invoking a transaction is nice and clean, the implementation is a little arbitrary. Using the **yield** keyword in any arbitrary method brings a Perlisism to mind: *Beware the Turing Tar Pit, where everything is possible, but nothing of interest is easy.* The yield keyword makes wrapping code in a transaction possible, as well as iterating over a collection possible, implementing a Kestrel, and many other idioms.
81 Here's the source for #transaction in Rails' ActiveRecord::ConnectionAdapters::DatabaseStatements module:
83 def transaction(options = {})
84 options.assert_valid_keys :requires_new, :joinable
86 last_transaction_joinable = @transaction_joinable
87 if options.has_key?(:joinable)
88 @transaction_joinable = options[:joinable]
89 else
90 @transaction_joinable = true
91 end
92 requires_new = options[:requires_new] || !last_transaction_joinable
94 transaction_open = false
95 begin
96 if block_given?
97 if requires_new || open_transactions == 0
98 if open_transactions == 0
99 begin_db_transaction
100 elsif requires_new
101 create_savepoint
102 end
103 increment_open_transactions
104 transaction_open = true
105 end
106 yield
107 end
108 rescue Exception => database_transaction_rollback
109 if transaction_open && !outside_transaction?
110 transaction_open = false
111 decrement_open_transactions
112 if open_transactions == 0
113 rollback_db_transaction
114 else
115 rollback_to_savepoint
116 end
117 end
118 raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
119 end
120 ensure
121 @transaction_joinable = last_transaction_joinable
123 if outside_transaction?
124 @open_transactions = 0
125 elsif transaction_open
126 decrement_open_transactions
127 begin
128 if open_transactions == 0
129 commit_db_transaction
130 else
131 release_savepoint
132 end
133 rescue Exception => database_transaction_rollback
134 if open_transactions == 0
135 rollback_db_transaction
136 else
137 rollback_to_savepoint
138 end
139 raise
140 end
141 end
142 end
144 If you study it, you can see the **yield** buried in the middle and work out that this method exists to do some work before and after the block, just like the combinator we constructed. This is the (current) idiomatic way to wrap some work with side effects executed before and after your code: write a method that takes a block and does the wrapping around the yield keyword.
146 The obvious challenge with this approach is that it is so ad hoc. having written code for database transactions, if you want to do something similar for persistent file storage (like open a file before using it and then close it when you're done), you need to write the wrapping code all over again. For example, if you wanted to write your own wrapper around Ruby's IO class that opened a file for writing before executing your block then flushed its buffers and closed the file at the end, you'd have to repeat the entire pattern.
148 As we saw in [Refactoring Methods with Recursive Combinators]( and [Practical Recursive Combinators](, it is also possible to write Ruby combinators to encapsulate the pattern of wrapping a block explicitly when writing methods. We also saw in [Aspect-Oriented Programming in Ruby using Combinator Birds]( that you can declaratively wrap a method using method advice. There are many roads to the summit :-)
fe91187 Reg Braithwaite mockingbird links and removing the "NEW!"
raganwald authored
150 _More on combinators_: [Kestrels](, [The Thrush](, [Songs of the Cardinal](, [Quirky Birds and Meta-Syntactic Programming](, [Aspect-Oriented Programming in Ruby using Combinator Birds](, [The Enchaining and Obdurate Kestrels](, [Finding Joy in Combinators](, [Refactoring Methods with Recursive Combinators](, [Practical Recursive Combinators](, [The Hopelessly Egocentric Blog Post](, [Wrapping Combinators](, and [Mockingbirds and Simple Recursive Combinators in Ruby](
0f97bf0 Reg Braithwaite Wrapping Combinators
raganwald authored
7d4a695 Reg Braithwaite dasherize the mores
raganwald authored
152 ---
f69ce70 Reg Braithwaite Link to the book
raganwald authored
8c4fb37 Reg Braithwaite My recent work
raganwald authored
154 My recent work:
f9d4487 Reg Braithwaite allongé
raganwald authored
156 ![]([![JavaScript Allongé](]( "JavaScript Allongé")![]([![CoffeeScript Ristretto](]( "CoffeeScript Ristretto")![]([![Kestrels, Quirky Birds, and Hopeless Egocentricity](]( "Kestrels, Quirky Birds, and Hopeless Egocentricity")
4479986 Reg Braithwaite More recent work!
raganwald authored
f9d4487 Reg Braithwaite allongé
raganwald authored
158 * [JavaScript Allongé](, [CoffeeScript Ristretto](, and my [other books](
21fcc00 Reg Braithwaite redirect to
raganwald authored
159 * [](, practical function combinators and decorators for JavaScript.
17be325 Reg Braithwaite revised footers
raganwald authored
160 * [Method Combinators](, a CoffeeScript/JavaScript library for writing method decorators, simply and easily.
4f7cce6 Reg Braithwaite githiub
raganwald authored
161 * [jQuery Combinators](, what else? A jQuery plugin for writing your own fluent, jQuery-like code.
f69ce70 Reg Braithwaite Link to the book
raganwald authored
16fa4bb Reg Braithwaite separated .sig
raganwald authored
163 ---
b2b5c4a Reg Braithwaite shuffle
raganwald authored
165 (Spot a bug or a spelling mistake? This is a Github repo, fork it and send me a pull request!)
7e71700 Reg Braithwaite updated links
raganwald authored
167 [Reg Braithwaite]( | [@raganwald](
Something went wrong with that request. Please try again.