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 !
edited for clarity
Reg Braithwaite (author)
Fri Nov 21 19:28:25 -0800 2008
commit  72ab8c80858758f79746b1b982dac8f9c5bdc8af
tree    36b38ef9497e1f78dafaa07f4551b726c2bee99c
parent  0bdff294c77f2fe38c40b58ba09206e742448998
...
23
24
25
26
27
28
29
30
31
32
33
34
 
 
 
 
 
 
 
35
36
37
38
39
 
40
41
42
43
44
 
45
46
47
48
 
49
50
51
...
64
65
66
67
68
69
 
 
 
 
...
23
24
25
 
 
 
 
 
 
 
 
 
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
43
44
45
46
47
48
49
50
51
...
64
65
66
 
 
67
68
69
70
71
0
@@ -23,29 +23,29 @@
0
 # 
0
 # http://www.opensource.org/licenses/mit-license.php
0
 
0
-def divide_and_conquer(steps)
0
-  lambda do |value|
0
-    if steps[:divisible?].call(value)
0
-      steps[:recombine].call(
0
-        steps[:divide].call(value).map { |sub_value| divide_and_conquer(steps).call(sub_value) }
0
-      )
0
-    else
0
-      steps[:conquer].call(value)
0
-    end
0
+def divide_and_conquer(value, steps)
0
+  if steps[:divisible?].call(value)
0
+    steps[:recombine].call(
0
+      steps[:divide].call(value).map { |sub_value| divide_and_conquer(sub_value, steps) }
0
+    )
0
+  else
0
+    steps[:conquer].call(value)
0
   end
0
 end
0
 
0
 def sum_squares(list)
0
   divide_and_conquer(
0
+    list,
0
     :divisible? => lambda { |value| value.kind_of?(Enumerable) },
0
     :conquer    => lambda { |value| value ** 2 },
0
     :divide     => lambda { |value| value },
0
     :recombine  => lambda { |list| list.inject() { |x,y| x + y } }
0
-  ).call(list)
0
+  )
0
 end
0
 
0
 def rotate(square)
0
   divide_and_conquer(
0
+    square,
0
     :divisible? => lambda { |value| value.kind_of?(Enumerable) && value.size > 1 },
0
     :conquer => lambda { |value| value },
0
     :divide => lambda do |square|
0
@@ -64,5 +64,7 @@ def rotate(square)
0
       upper_right.zip(lower_right).map { |l,r| l + r } +
0
       upper_left.zip(lower_left).map { |l,r| l + r }
0
     end
0
-  ).call(square)
0
-end
0
\ No newline at end of file
0
+  )
0
+end
0
+
0
+p rotate([[1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16]])
...
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
27
28
29
...
180
181
182
183
 
 
 
184
185
 
 
 
 
 
 
 
 
186
187
188
...
190
191
192
193
 
 
 
194
195
196
197
198
199
 
200
201
202
...
215
216
217
218
 
 
 
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
 
 
 
 
 
 
 
234
235
236
...
238
239
240
 
241
242
243
244
245
 
246
247
248
...
250
251
252
 
253
254
255
...
268
269
270
271
 
272
273
274
275
276
277
 
278
279
280
...
298
299
300
301
 
302
303
 
304
305
 
306
307
308
...
5
6
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
9
10
11
 
12
13
14
15
...
166
167
168
 
169
170
171
172
 
173
174
175
176
177
178
179
180
181
182
183
...
185
186
187
 
188
189
190
191
192
193
194
195
 
196
197
198
199
...
212
213
214
 
215
216
217
218
219
220
221
222
223
 
 
 
 
 
 
 
 
 
224
225
226
227
228
229
230
231
232
233
...
235
236
237
238
239
240
241
242
 
243
244
245
246
...
248
249
250
251
252
253
254
...
267
268
269
 
270
271
272
273
274
275
 
276
277
278
279
...
297
298
299
 
300
301
 
302
303
 
304
305
306
307
0
@@ -5,25 +5,11 @@ In previous commits, we have met some of [Combinatory Logic](http://en.wikipedia
0
 
0
 > As explained in [Kestrels](http://github.com/raganwald/homoiconic/tree/master/2008-10-29/kestrel.markdown), 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.
0
 
0
-The thrush, cardinal, quirky bird and bluebird all either permute or group their arguments. Although they do change the structure of their arguments, they conserve them: if you pass `xyz` to a bluebird, you get one `x`, one `y`, and one `z` back, exactly what you passed in. The only difference is, you get `x(yz)` back, so they have been grouped for you. But nothing has been added and nothing has been taken away. Alone amongst the combinators we've examined, the kestrel does not conserve its arguments. It *erases* one. If you pass `xy` to a kestrel, you only get `x` back. The `y` is erased. The kestrel comes from a different family of combinators than the others.
0
-
0
-Today we are going to meet another combinator that does not conserve its arguments, the Double Mockingbird. Where a kestrel erases one of its arguments, the double mockingbird *duplicates* both of its arguments. In logic notation, `M`<sub>2</sub>`xy = xy(xy)`. Or in Ruby:
0
-
0
-  double_mockingbird.call(x).call(y)
0
-    => x.call(y).call(x.call(y))
0
-
0
-The double mockingbird is not the only combinator that duplicates one or more of its arguments. Logicians have also found important uses for many other duplicating combinators like the ordinary Mockingbird (`Mx = xx`), which is the simplest duplicating combinator, the Starling (`Sxyz = xz(yz)`), which is one half of the [SK combinator calculus](http://en.wikipedia.org/wiki/SKI_combinator_calculus "SKI combinator calculus - Wikipedia, the free encyclopedia"), and the Turing Bird (`Uxy = y(xxy)`), which is named after [its discoverer](http://www.alanturing.net/turing_archive/index.html "Alan Turing (1912-1954)").
0
-
0
-Before we figure out what duplicating combinators can do for us, let's review a popular object-oriented "pattern:"
0
-
0
-Template Methods
0
----
0
-
0
 One popular pattern in object-oriented programming is the [Template Method](http://en.wikipedia.org/wiki/Template_method_pattern "Template method pattern - Wikipedia, the free encyclopedia"):
0
 
0
 > In object-oriented programming, first a class is created that provides the basic steps of an algorithm design. These steps are implemented using abstract methods. Later on, subclasses change the abstract methods to implement real actions. Thus the general algorithm is saved in one place but the concrete steps may be changed by the subclasses.
0
 
0
-So there you have it: Template methods help us separate the basic steps of a general algorithm from the concrete steps of a specific algorithm. With a template method, we build a template or framework method with holes in it, then "fill in the blanks" by implementing methods for concrete steps.
0
+Template methods help us separate the basic steps of a general algorithm from the concrete steps of a specific algorithm. With a template method, we build a template or framework method with holes in it, then "fill in the blanks" by implementing methods for concrete steps.
0
 
0
 Let's whistle up an example. The template we're going to use is called [Divide and Conquer](http://www.cs.berkeley.edu/~vazirani/algorithms/chap2.pdf). It's a general algorithm for solving problems by breaking them up into sub-problems. In Ruby, we might write:
0
   
0
@@ -180,9 +166,18 @@ Naturally, there's an easier way in Ruby, and once again it involves a combinato
0
 Double Mockingbirds
0
 ---
0
 
0
-As mentioned above, double mockingbirds have a duplicative effect: `M`<sub>2</sub>`xy = xy(xy)`. The great benefit of duplicative combinators from a *theoretical* perspective is that combinators that duplicate an argument can be used to introduce recursion without names, scopes, bindings, and other things that clutter things up. Being able to introduce anonymous recursion is very elegant, and [there are times when it is useful in its own right](http://www.eecs.harvard.edu/~cduan/technical/ruby/ycombinator.shtml "A Use of the Y Combinator in Ruby").
0
+Almost all of the combinators we've seen so far conserve their arguments. For example, if you pass `xyz` to a bluebird, you get one `x`, one `y`, and one `z` back, exactly what you passed in. You get `x(yz)` back, so they have been grouped for you. But nothing has been added and nothing has been taken away. Likewise the thrush reverses its arguments, but again it answers back the same number arguments you passed to it.
0
+
0
+Alone amongst the combinators we've examined, the kestrel does not conserve its arguments. It *erases* one. If you pass `xy` to a kestrel, you only get `x` back. The `y` is erased. Kestrels do not conserve their arguments. Today we are going to meet another combinator that does not conserve its arguments, the Double Mockingbird. Where a kestrel erases one of its arguments, the double mockingbird *duplicates* both of its arguments. In logic notation, `M`<sub>2</sub>`xy = xy(xy)`. Or in Ruby:
0
 
0
-Nice as that is, we aren't going to celebrate the elegance of anonymous recursion. But let's quickly review how it works before we take a look at the practical benefits of using a combinator to generate divide and conquer methods. Let's start by writing a double mockingbird in Ruby:
0
+  double_mockingbird.call(x).call(y)
0
+    => x.call(y).call(x.call(y))
0
+
0
+The double mockingbird is not the only combinator that duplicates one or more of its arguments. Logicians have also found important uses for many other duplicating combinators like the ordinary Mockingbird (`Mx = xx`), which is the simplest duplicating combinator, the Starling (`Sxyz = xz(yz)`), which is one half of the [SK combinator calculus](http://en.wikipedia.org/wiki/SKI_combinator_calculus "SKI combinator calculus - Wikipedia, the free encyclopedia"), and the Turing Bird (`Uxy = y(xxy)`), which is named after [its discoverer](http://www.alanturing.net/turing_archive/index.html "Alan Turing (1912-1954)").
0
+
0
+> The great benefit of duplicative combinators from a *theoretical* perspective is that combinators that duplicate an argument can be used to introduce recursion without names, scopes, bindings, and other things that clutter things up. Being able to introduce anonymous recursion is very elegant, and [there are times when it is useful in its own right](http://www.eecs.harvard.edu/~cduan/technical/ruby/ycombinator.shtml "A Use of the Y Combinator in Ruby").
0
+
0
+Let's write a double mockingbird in Ruby:
0
 
0
   m2 = lambda do |x|
0
     lambda do |y|
0
@@ -190,13 +185,15 @@ Nice as that is, we aren't going to celebrate the elegance of anonymous recursio
0
     end
0
   end
0
 
0
-Ok, let's try it with our summing the values in a nested list example from above. Now a double mockingbird only has two parameters, and our template method has four concrete steps, so we are going to have to combine elements together. We'll start with `conquer_if_divisible`:
0
+We'll use it to sum the squares of a nested list. We're going to construct an algorithm using a divide and conquer strategy, but to keep the code clear we'll make things a little simpler than the template method example above. Instead of four separate concrete steps, we'll use just two: One to "conquer" a value if possible and another to divide the value up, recursively attempt to conquer the sub-values, and recombine them together.
0
+
0
+Here's the first of our two steps, `conquer_if_divisible`:
0
 
0
   conquer_if_divisible = lambda do |value|
0
     value ** 2 unless value.kind_of?(Enumerable)
0
   end
0
 
0
-As you can see, it combines the `divisible?` and `conquer` methods from above. Now we'll write a function that incorporates the rest of our divide and conquer strategy: `divide` and `recombine` along with some ceremony for recursion:
0
+And here's the second of our two steps, a function that incorporates the rest of our divide and conquer strategy: `divide` and `recombine` along with some ceremony for recursion:
0
 
0
   conquer_or_divide_and_try_again = lambda do |conquer_if_divisible|
0
     lambda do |myself|
0
@@ -215,22 +212,22 @@ You can work this out one line at a time. But the result is pleasing:
0
   sum_the_squares.call([1, 2, 3, [[4,5], 6], [[[7]]]])
0
     => 140
0
 
0
-The double mocking bird does two things: First, because it incorporates `x.call(y)`, it allows us to break an algorithm into two separate concrete steps. Second, because it takes the resulting function and calls the function with itself, the function can call itself recursively. There are more elegant ways to accomplish recursion, but for our purposes, the important thing is that we can accomplish it without cluttering up the namespace. All we needed was a combinator that (a) duplicated its arguments so we could build a recursive function, and (b) had more than one argument so that we could break an algorithm up into separate steps.
0
+The double mocking bird does two things: First, because it incorporates `x.call(y)`, it allows us to break an algorithm into two separate concrete steps. Second, because it takes the resulting function and calls the function with itself, the function can call itself recursively.
0
+
0
+There are more elegant ways to accomplish recursion, but for our purposes, the important thing is that we can accomplish it without cluttering up the namespace. The double mockingbird provides a simple way of building divide and conquer algorithms out of one function that handles division, recursion, and recombination, and another function that handles conquering.
0
 
0
 Helpers
0
 ---
0
 
0
 Building a recursive function like a divide and conquer algorithm is feasible with combinators, but that making it recurse anonymously adds some accidental complexity we do not need for things like summing squares or rotating matrices. But there's something there worth using on a day-to-day basis: What if instead of building a template method from the top down by specializing the concrete steps, we construct the method from the bottom up using a helper method that works like a combinator?
0
 
0
-  def divide_and_conquer(steps)
0
-    lambda do |value|
0
-      if steps[:divisible?].call(value)
0
-        steps[:recombine].call(
0
-          steps[:divide].call(value).map { |sub_value| divide_and_conquer(steps).call(sub_value) }
0
-        )
0
-      else
0
-        steps[:conquer].call(value)
0
-      end
0
+  def divide_and_conquer(value, steps)
0
+    if steps[:divisible?].call(value)
0
+      steps[:recombine].call(
0
+        steps[:divide].call(value).map { |sub_value| divide_and_conquer(sub_value, steps) }
0
+      )
0
+    else
0
+      steps[:conquer].call(value)
0
     end
0
   end
0
   
0
@@ -238,11 +235,12 @@ Now you can build any method you like using it:
0
 
0
   def sum_squares(list)
0
     divide_and_conquer(
0
+      list,
0
       :divisible? => lambda { |value| value.kind_of?(Enumerable) },
0
       :conquer    => lambda { |value| value ** 2 },
0
       :divide     => lambda { |value| value },
0
       :recombine  => lambda { |list| list.inject() { |x,y| x + y } }
0
-    ).call(list)
0
+    )
0
   end
0
   
0
   sum_squares([1, 2, 3, [[4,5], 6], [[[7]]]])
0
@@ -250,6 +248,7 @@ Now you can build any method you like using it:
0
   
0
   def rotate(square)
0
     divide_and_conquer(
0
+      square,
0
       :divisible? => lambda { |value| value.kind_of?(Enumerable) && value.size > 1 },
0
       :conquer => lambda { |value| value },
0
       :divide => lambda do |square|
0
@@ -268,13 +267,13 @@ Now you can build any method you like using it:
0
         upper_right.zip(lower_right).map { |l,r| l + r } +
0
         upper_left.zip(lower_left).map { |l,r| l + r }
0
       end
0
-    ).call(square)
0
+    )
0
   end
0
 
0
   rotate([[1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16]])
0
     => [[4, 8, 12, 16], [3, 7, 11, 15], [2, 6, 10, 14], [1, 5, 9, 13]]
0
 
0
-Neat-o. But all this work just to suggest using helper methods? Honestly?? Well, if you're enthusiastic about meta-programming, by all means use some of the techniques discussed in posts like [Quirky Birds and Meta-Syntactic Programming](http://github.com/raganwald/homoiconic/tree/master/2008-11-04/quirky_birds_and_meta_syntactic_programming.markdown) so you can write something like:
0
+Neat-o. But all this work just to suggest using helper methods? Honestly?? Well, if you're enthusiastic about meta-programming, by all means use some of the techniques discussed in posts like [Quirky Birds and Meta-Syntactic Programming](http://github.com/raganwald/homoiconic/tree/master/2008-11-04/quirky_birds_and_meta_syntactic_programming.markdown) so that instead of writing a `#rotate` method and calling a helper, you can write something like:
0
   
0
   def_divide_and_conquer(
0
     :rotate,
0
@@ -298,11 +297,11 @@ Neat-o. But all this work just to suggest using helper methods? Honestly?? Well,
0
     end
0
   )
0
 
0
-Instead of writing a `#rotate` method and calling a helper. But if you're looking for an insight about helper methods, it's this: *Parameterizing a helper method with functions lets us re-use the general form of algorithms and specialize the concrete steps without a lot of extra inheritance baggage*.
0
+But if you're looking for an insight about helper methods, it's this: *Parameterizing a helper method with functions lets us re-use the general form of algorithms and specialize the concrete steps without a lot of extra inheritance baggage*.
0
 
0
-You get another win as well: When we wrote our generic template method, we only knew it was a divide and conquer algorithm because we said it was. In an actual code base, its name would bear very little resemblance to its form, because we try to name things by what they do, not how they work. Furthermore, developers would have to deduce that it is a divide and conquer algorithm through examination and painstaking review. This is sometimes difficult with a recursive algorithm. By creating a `divide_and_conquer` helper method, we document every method that uses it, whether they be called `sum_squares` or `rotate`. And furthermore, the most difficult part to understand, the recursion mechanism, is clearly separated from the specific concrete steps. It is a very fine example of abstraction.
0
+You get another win as well: When we wrote our generic template method, we only knew it was a divide and conquer algorithm because we said it was. In an actual code base, its name would bear very little resemblance to its form, because we try to name things by what they do, not how they work. Furthermore, developers would have to deduce that it is a divide and conquer algorithm through examination and painstaking review. This is sometimes difficult with a recursive algorithm. By creating a `divide_and_conquer` helper method, we document every method that uses it, whether they be called `sum_squares` or `rotate`. And furthermore, the most difficult part to understand, the recursion mechanism, is clearly separated from the specific concrete steps. It is an example of abstraction.
0
 
0
-So there we have it: given a general-purpose algorithm like divide and conquer, both template methods and paramaterizing helper methods with functions allow us to separate the re-usable general form of the algorithm from the specific concrete steps. The template method does not give us re-use of the general form for each method sharing the same general algorithm, but it does make it easy to specialize the concrete steps in a polymorphic way. Paramaterizing a helper method with functions does allow us to abstract and re-use the same general algorithm across multiple methods but does not support specializing the concrete steps in a polymorphic way.
0
+Given a general-purpose algorithm like divide and conquer, both template methods and paramaterizing helper methods with functions allow us to separate the re-usable general form of the algorithm from the specific concrete steps. The template method does not give us re-use of the general form for each method sharing the same general algorithm, but it does make it easy to specialize the concrete steps in a polymorphic way. Paramaterizing a helper method with functions does allow us to abstract and re-use the same general algorithm across multiple methods but does not support specializing the concrete steps in a polymorphic way.
0
 
0
 Have fun!
0
 

Comments