Gray Soft / Tags / Functional Programmingtag:graysoftinc.com,2014-03-20:/tags/Functional%20Programming2014-04-25T21:40:34ZJames Edward Gray IIThe Right Ruby Mixtag:graysoftinc.com,2012-03-01:/posts/1022014-04-25T21:40:34ZIn this article I look at some of the influences of the Ruby language and use those to decide how we should be writing Ruby code.<p>Ruby is a melting pot language. It borrows ideas from many things that came before. It combines several different programming philosophies.</p>
<p>This aspect of the language can be a plus. It means that Ruby is suited to multiple applications. It also opens up some pragmatic shortcuts. Even better, it sometimes encourages us to think about problems using a different lens of thought.</p>
<p>Of course, this cuts both ways. Ruby living at the intersection of many ideas does have some downsides. First, there's more to learn than you find with some simpler languages. There's a cost for the extra knowledge we have to track. Even worse though, in my opinion, is that it's sometimes hard to know exactly what Ruby's style really is.</p>
<h4>Going Off Script</h4>
<p>One culture Ruby borrowed heavily from is that of the so called "Scripting Languages." The main source of these features was Perl, in my opinion, but you can also find influences from Bash and other sources. I found this comforting since I came to Ruby from Perl, but the truth is that <a href="https://twitter.com/#!/garybernhardt/status/172080693021655040">it bothers some people</a>.</p>
<p>What's up with that <code>test()</code> method, really?</p>
<p>One thought is that Ruby has it to make Bash programmers more comfortable. In other words, this Bash code:</p>
<pre><code>$ if [[ -e "Gemfile" ]]; then echo "This project uses Bundler."; fi
This project uses Bundler.
</code></pre>
<p>can be ported to similar Ruby code:</p>
<pre><code>$ ruby -e 'if test ?e, "Gemfile" then puts "This project uses Bundler." end'
This project uses Bundler.
</code></pre>
<p>That's not a very Rubyish way to check for a file though. Typically you would see it written more like this:</p>
<pre><code>$ ruby -e 'puts "This project uses Bundler." if File.exist?("Gemfile")'
This project uses Bundler.
</code></pre>
<p>That's shorter and it reads better, so it's no surprise that Rubyists prefer it. We generally love code that reads well.</p>
<p>The interface to <code>test()</code> is still a little bizarre though, don't you think? I guess we wanted something close to <code>-e</code> and Ruby's character syntax <code>?e</code> was deemed close enough. The truth is that we could get closer to the Bash syntax, if we really wanted to:</p>
<pre><code>$ ruby -r ./bash -e 'if bash_test [[ -e, "Gemfile" ]] then puts "..." end'
...
$ cat bash.rb
%w[A b c C d e f g G k l M o O p r R s S u w W x X z].each do |arg|
flag = -arg.getbyte(0)
if arg.downcase == arg
Object.class_eval do
define_method(arg) { flag }
end
else
Object.const_set(arg, flag)
end
end
def bash_test(*args)
test(*args.flatten)
end
</code></pre>
<p>Why didn't Ruby go this far? I assume the reasons are pretty obvious, but let's discuss them briefly.</p>
<p>First, it pollutes the top level namespace with several methods and constants. This already bit me in the creation of this tiny example. I had a bug in my first attempt at this code and I tried to debug it with <code>p()</code> only to realize that I had replaced that method with a much less helpful chunk of code. (If you ever run into that problem, you can still get to it with <code>Kernel.p()</code> thanks to the magic of <code>module_function()</code>.) The added methods and constants return almost senseless values, so it's unlikely they would be good for much else.</p>
<p>Another problem is that I couldn't support all of the operators <code>test()</code> accepts using this trick. It also understands <code>?-</code>, <code>?=</code>, <code>?<</code>, and <code>?></code>.</p>
<p>We can see that <code>test()</code> was a compromise, to get close to some Bash-like support without doing too much damage to Ruby itself. Was it worth it? Tough call. I favor methods like <code>File.exist?()</code> and the <code>File::Stat</code> module when I do checks, because I think they better reveal my intentions and they don't make the code a lot longer.</p>
<p>Now, I do use several of the borrowed Perl shortcuts. You probably know that, since <a href="/rubies-in-the-rough/the-wrong-tool-for-the-job">I have written about them before</a>. I find that using Ruby on the command-line with <code>-n</code> or <code>-p</code>, <code>$_</code>, and the flip-flop operator does save me a lot of code and time. In those cases, the tradeoff feels worth it to me.</p>
<p>But perhaps this just shows that I was more of a Perl guy than a Bash guy when I came to Ruby.</p>
<p>Do I use these crazy shortcuts when I'm writing full Ruby programs? Not usually. I am not above shelling out to a command when I think it's easier though. For example, I've seen several complex and slow ways to generate a UUID in Ruby over the years, but I usually favor this cheat:</p>
<pre><code>$ ruby -e 'uuid = `uuidgen`.strip; p uuid'
"6196D647-F6C4-4764-90A8-1775E22400B9"
</code></pre>
<p>It's been fast enough for any need I've ever had and I haven't had to run the code anywhere that didn't support this trick. (Windows probably wouldn't out of the box.)</p>
<p>This is the real win of Ruby's scripting heritage, in my opinion. It allows us to think about some problems in a different way. Instead of being forced to think, "I need to find a library for generating UUID's," Ruby makes it easy to substitute, "Doesn't Unix have a standard tool for this that I can talk to?" Would you try the same trick in a language like Java? Probably not, just because scripting support is a lot less robust there. That's why they tend to favor the libraries.</p>
<p>I vote we keep the I-can-always-fall-back-to-scripting attitude.</p>
<h4>Life After Functional Programming</h4>
<p>Does Ruby inherit from the functional programming languages? I would say that it definitely does. The functional languages are really where concepts like iterators originated. In fact, I believe Ruby's blocks in general were inspired by <a href="http://en.wikipedia.org/wiki/Thunk_(functional_programming)">functional concepts</a>.</p>
<p>Does that mean Ruby is a functional programming language? That question is a lot tougher, but I lean towards answering, "No." There are definitely <a href="http://www.randomhacks.net/articles/2005/12/03/why-ruby-is-an-acceptable-lisp">folks that disagree with me on this point</a> though.</p>
<p>Ruby methods aren't quite first-class functions. However, you can pretty much simulate that style of programming if you stick to using <code>lambda()</code>. That allows us to store "functions" in variables:</p>
<div class="highlight highlight-ruby"><pre><span class="n">multiply</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="p">,</span> <span class="n">r</span><span class="o">|</span> <span class="n">l</span> <span class="o">*</span> <span class="n">r</span> <span class="p">}</span>
<span class="n">double</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span> <span class="n">multiply</span><span class="o">[</span><span class="n">n</span><span class="p">,</span> <span class="mi">2</span><span class="o">]</span> <span class="p">}</span>
<span class="n">triple</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span> <span class="n">multiply</span><span class="o">[</span><span class="n">n</span><span class="p">,</span> <span class="mi">3</span><span class="o">]</span> <span class="p">}</span>
<span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="n">.</span><span class="mi">5</span><span class="p">)</span><span class="o">.</span><span class="n">each_with_index</span> <span class="k">do</span> <span class="o">|</span><span class="n">n</span><span class="p">,</span> <span class="n">i</span><span class="o">|</span>
<span class="nb">puts</span> <span class="k">unless</span> <span class="n">i</span><span class="o">.</span><span class="n">zero?</span>
<span class="nb">puts</span> <span class="s2">" N: </span><span class="si">#{</span><span class="n">n</span><span class="si">}</span><span class="s2">"</span>
<span class="nb">puts</span> <span class="s2">"Doubled: </span><span class="si">#{</span><span class="n">double</span><span class="o">[</span><span class="n">n</span><span class="o">]</span><span class="si">}</span><span class="s2">"</span>
<span class="nb">puts</span> <span class="s2">"Tripled: </span><span class="si">#{</span><span class="n">triple</span><span class="o">[</span><span class="n">n</span><span class="o">]</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
</pre></div>
<p>Because the block passed to <code>lambda()</code> is a closure, we can later refer to those functions by name.</p>
<p>We can also return functions from functions:</p>
<div class="highlight highlight-ruby"><pre><span class="n">build_multiplier</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">multiplier</span><span class="o">|</span>
<span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span> <span class="n">multiplier</span> <span class="o">*</span> <span class="n">n</span> <span class="p">}</span>
<span class="p">}</span>
<span class="n">double</span> <span class="o">=</span> <span class="n">build_multiplier</span><span class="o">[</span><span class="mi">2</span><span class="o">]</span>
<span class="n">triple</span> <span class="o">=</span> <span class="n">build_multiplier</span><span class="o">[</span><span class="mi">3</span><span class="o">]</span>
<span class="c1"># ...</span>
</pre></div>
<p>Of course, we can also pass functions to functions:</p>
<div class="highlight highlight-ruby"><pre><span class="n">poor_mans_curry</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">arg</span><span class="p">,</span> <span class="n">f</span><span class="o">|</span>
<span class="nb">lambda</span> <span class="p">{</span> <span class="o">|*</span><span class="n">rest</span><span class="o">|</span> <span class="n">f</span><span class="o">[</span><span class="n">arg</span><span class="p">,</span> <span class="o">*</span><span class="n">rest</span><span class="o">]</span> <span class="p">}</span>
<span class="p">}</span>
<span class="n">double</span> <span class="o">=</span> <span class="n">poor_mans_curry</span><span class="o">[</span><span class="mi">2</span><span class="p">,</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="p">,</span> <span class="n">r</span><span class="o">|</span> <span class="n">l</span> <span class="o">*</span> <span class="n">r</span> <span class="p">}</span><span class="o">]</span>
<span class="n">triple</span> <span class="o">=</span> <span class="n">poor_mans_curry</span><span class="o">[</span><span class="mi">3</span><span class="p">,</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="p">,</span> <span class="n">r</span><span class="o">|</span> <span class="n">l</span> <span class="o">*</span> <span class="n">r</span> <span class="p">}</span><span class="o">]</span>
<span class="c1"># ...</span>
</pre></div>
<p>The example above also shows that anonymous functions are supported.</p>
<p>Ruby even has features to support this kind of work in some ways. I could have written the first example as:</p>
<div class="highlight highlight-ruby"><pre><span class="n">multiply</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="p">,</span> <span class="n">r</span><span class="o">|</span> <span class="n">l</span> <span class="o">*</span> <span class="n">r</span> <span class="p">}</span>
<span class="n">double</span> <span class="o">=</span> <span class="n">multiply</span><span class="o">.</span><span class="n">curry</span><span class="o">[</span><span class="mi">2</span><span class="o">]</span>
<span class="n">triple</span> <span class="o">=</span> <span class="n">multiply</span><span class="o">.</span><span class="n">curry</span><span class="o">[</span><span class="mi">3</span><span class="o">]</span>
<span class="c1"># ...</span>
</pre></div>
<p>Since we've established that Ruby supports all of these functional features, why do I say it's not a functional language? Well, it's true that we can compose with functions in Ruby. That's pretty much the gateway to functional programming. But things don't end there.</p>
<p>Once you have those features, you start to do things in a certain way to maximize the benefits of using them. For example, it's ideal to use immutable data structures and treat variables more like constants. This allows you to guarantee that the same function called with the same arguments always returns the same results.</p>
<p>It's also very common to write recursive functions when programming functionally, so it's nice for the language to support that.</p>
<p>These are examples of areas where Ruby stops scoring so well. Of course, you can make immutable data structures and write recursive functions. However, there are downsides to using these tactics. Ruby's GC isn't really tuned for immutable data structures and you can definitely take some performance hits while the language struggles to clean up after you. Similarly, Ruby doesn't do tail-call optimization by default, so it's easy to exhaust the stack with a recursive function.</p>
<p>In my opinion, details like these make it clear that Ruby wasn't designed to favor this style of programming, even if it does make it possible. That's why I say that it's not really a functional language.</p>
<p>In Ruby, we could write a function to wrap another function and add some feature: say memoization. But the fact is that Ruby often has other ways to accomplish these tasks. For example, here's a dirt simple memoization technique for Ruby:</p>
<div class="highlight highlight-ruby"><pre><span class="k">def</span> <span class="nf">fibonacci</span><span class="p">(</span><span class="n">nth</span><span class="p">,</span> <span class="n">series</span> <span class="o">=</span> <span class="nb">method</span><span class="p">(</span><span class="ss">:fibonacci</span><span class="p">))</span>
<span class="k">if</span> <span class="n">nth</span> <span class="o"><</span> <span class="mi">2</span>
<span class="n">nth</span>
<span class="k">else</span>
<span class="n">series</span><span class="o">[</span><span class="n">nth</span> <span class="o">-</span> <span class="mi">2</span><span class="o">]</span> <span class="o">+</span> <span class="n">series</span><span class="o">[</span><span class="n">nth</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># The easiest memoization in the world:</span>
<span class="n">fibs</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">series</span><span class="p">,</span> <span class="n">nth</span><span class="o">|</span> <span class="n">series</span><span class="o">[</span><span class="n">nth</span><span class="o">]</span> <span class="o">=</span> <span class="n">fibonacci</span><span class="p">(</span><span class="n">nth</span><span class="p">,</span> <span class="n">series</span><span class="p">)</span> <span class="p">}</span>
<span class="nb">p</span> <span class="n">fibs</span><span class="o">[</span><span class="mi">100</span><span class="o">]</span>
</pre></div>
<p>The "function" above is originally recursive, though probably not as you normally see them written. I allow for the source of the series to be passed as an argument. That was just to make it trivial to swap it out later and that argument defaults to a reference of the method (Ruby's terminology) itself. This means it's really normal recursion (in disguise).</p>
<p>The main point here is that the default block of a <code>Hash</code> is actually just a memoization function already written for us in optimized C. We can just use a <code>Hash</code> to achieve that effect. It's clean and lightning quick.</p>
<p>I've seen multiple Rubyists over the years take detours into the functional languages, then come back and try to apply the concepts they have learned to Ruby itself. I did the same thing myself and <a href="/higher-order-ruby">wrote a lot about it</a>. I never ended up finishing that series, because I realized that it wasn't really working. If I followed the functional code too closely, the resulting Ruby had issues. In other cases I really liked the code I came up with, but that's just because I translated the underlying concepts to how Ruby would better handle them (leaving functional programming behind).</p>
<p>The moral is that Ruby did inherit a lot from the functional programming languages, but you are probably already using the important bits. Work with blocks and iterators where they make sense and you will gain the most benefit from that style. Going any further than that may work for some cases, but the path is full of pitfalls.</p>
<h4>It's Objects All the Way Down</h4>
<p>Did I save the best for last? You bet!</p>
<p>Ruby clearly inherits from object oriented languages, like Smalltalk. That's a major focus of the design of the language.</p>
<p>The fact is that it really paid off too. Ruby has a gorgeous object model due to features like:</p>
<ul>
<li>Almost everything is an object</li>
<li>Classes are objects too</li>
<li>Class definitions are just plain Ruby code being executed</li>
<li>Classes are open</li>
<li>Hooks like <code>method_missing()</code> and <code>const_missing()</code>
</li>
<li>Per object behavior via singleton classes</li>
<li>Mix-ins</li>
</ul><p>Sure, the system isn't perfect. We traded method overloading for dynamic typing (which is good and bad, in my opinion). We also don't really have keyword arguments. <em>[<strong>Update</strong>: I am finally satisfied with Ruby's keyword arguments as of Ruby 2.1.]</em> We do have techniques for getting around these deficiencies that generally seem to be good enough though.</p>
<p>What about multiple inheritance? Some say it's a feature. Others say it's a bug. It's definitely debatable, but Ruby seems dynamic enough to get by without it, in either case.</p>
<p>The fact is, whether by accidental or purposeful design, Ruby really has a terrific object model. The more I've learned about it over the years, the more I have come to appreciate it. I mean it's almost a zen moment when you finally realize that Ruby's method lookup is just a straight line and tools like classes, singleton classes, and mix-ins allow you to define methods at desired points on that line. It all fits together so well.</p>
<p>What's more, as I have been a Rubyist, I have studied some of the more classical material on object orientation. The more I learn from that world, the better my Ruby seems to get. It's almost exactly the opposite of trying to apply functional techniques to the language in that Ruby seems to welcome such ideas.</p>
<p>For example, look at a simple pattern like <a href="http://sourcemaking.com/implementation-patterns/pluggable-selector">Pluggable Selector in Java</a>, a mainstream object oriented language. Now observe the same pattern in Ruby:</p>
<div class="highlight highlight-ruby"><pre><span class="k">class</span> <span class="nc">PluggableSelector</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">selector</span><span class="p">)</span>
<span class="vi">@selector</span> <span class="o">=</span> <span class="n">selector</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">run</span>
<span class="nb">send</span><span class="p">(</span><span class="s2">"run_</span><span class="si">#{</span><span class="vi">@selector</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">run_foo</span>
<span class="s2">"foo"</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">run_bar</span>
<span class="s2">"bar"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">result</span> <span class="o">=</span> <span class="no">PluggableSelector</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:foo</span><span class="p">)</span><span class="o">.</span><span class="n">run</span>
<span class="nb">puts</span> <span class="s2">"Ran </span><span class="si">#{</span><span class="n">result</span><span class="si">}</span><span class="s2">"</span>
</pre></div>
<p>Features like <code>Symbol</code> objects, interpolation, and <code>send()</code> just make this kind of work trivial. Ruby was made to do this.</p>
<p>Object thinking should always remain a big part of the Ruby we do.</p>
<h4>What's Our Style?</h4>
<p>I've argued that Ruby isn't just a scripting language, a functional language, or even just an object oriented language. It even has some other influences that I didn't visit here.</p>
<p>So, what kind of language is Ruby? What's the kind of programming that we do with it called?</p>
<p>I don't know that there is a name for it, but clearly it's a blend of many ideas. The trick is to find the right mix to make great code.</p>
<p>How do we define "great code?" Code's first priority is always to communicate with the reader. Start there.</p>
<p>Here's the mix I recommend for great Ruby:</p>
<ul>
<li>
<strong>74.41% Object Oriented Programming</strong>: the classic techniques enhanced by Ruby's dynamic nature</li>
<li>
<strong>17.21% Functional Programming</strong>: mostly the bits Ruby has adopted, like blocks and iterators</li>
<li>
<strong>9.38% Scripting</strong>: for quick hacks and pragmatic shortcuts</li>
</ul><p>You can clearly see by my exact figures and excellent math that this is a highly scientific calculation not to be argued with.</p>
<h4>Further Reading</h4>
<p>If you want to dig deeper into the various styles of programming, you might enjoy some of these resources:</p>
<ul>
<li>The best guide to tactical object oriented programming I have ever read is easily <a href="http://www.pearsonhighered.com/educator/product/Smalltalk-Best-Practice-Patterns/9780134769042.page">Smalltalk Best Practice Patterns</a>. It's worth learning a little Smalltalk just to read this book. It's that good.</li>
<li>You can often learn about using Ruby as a scripting language if you watch for articles that mention Ruby with a Unix slant. Here's <a href="http://tomayko.com/writings/unicorn-is-unix">a classic example</a> and <a href="http://jstorimer.com/2012/02/16/a-unix-shell-in-ruby.html">a newer one</a>.</li>
<li>I haven't read it yet, but <a href="http://pragprog.com/book/btlang/seven-languages-in-seven-weeks">Seven Languages in Seven Weeks</a> is on my reading list. It looks like a fun way to tour many different programming styles, including functional programming and prototype languages (which I didn't go into here).</li>
</ul>James Edward Gray IICurryingtag:graysoftinc.com,2006-02-22:/posts/102014-03-28T21:20:00ZIn this sixth article of the Higher-Order Ruby series, I examine currying by building up a currying library.<p>All the examples in this chapter are trivially translated (switch <code>sub { ... }</code> to <code>lambda { ... }</code>). Ironically, I have never seen a chunk of idiomatic Ruby do anything like this. Rubyists clearly favor blocks for this sort of work. Have a look at the stream addition and multiplication examples of this chapter, for example. You can also see this when MJD trying to create a suitable <code>inject()</code> for Perl (he calls it reduce/fold).</p>
<p>Another interesting point about this chapter is how much of it is spent warring with Perl's syntax. MJD really struggles to introduce a block-like syntax for curried methods and is outright defeated in a couple of attempts. I really like how easily Ruby yields to our attempts to reprogram her, in sharp contrast to her sister language.</p>
<p>Continuing that line of thought, here's my best effort at the Poor Man's Currying library:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/bin/env ruby -w</span>
<span class="k">class</span> <span class="nc">Proc</span>
<span class="k">def</span> <span class="nf">curry</span><span class="p">(</span><span class="o">&</span><span class="n">args_munger</span><span class="p">)</span>
<span class="nb">lambda</span> <span class="p">{</span> <span class="o">|*</span><span class="n">args</span><span class="o">|</span> <span class="n">call</span><span class="p">(</span><span class="o">*</span><span class="n">args_munger</span><span class="o">[</span><span class="n">args</span><span class="o">]</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">Object</span>
<span class="k">def</span> <span class="nf">curry</span><span class="p">(</span><span class="n">new_name</span><span class="p">,</span> <span class="n">old_name</span><span class="p">,</span> <span class="o">&</span><span class="n">args_munger</span><span class="p">)</span>
<span class="p">(</span><span class="o">[</span><span class="no">Class</span><span class="p">,</span> <span class="no">Module</span><span class="o">].</span><span class="n">include?</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="p">)</span> <span class="p">?</span> <span class="nb">self</span> <span class="p">:</span> <span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="p">)</span><span class="o">.</span><span class="n">class_eval</span> <span class="k">do</span>
<span class="n">define_method</span><span class="p">(</span><span class="n">new_name</span><span class="p">)</span> <span class="p">{</span> <span class="o">|*</span><span class="n">args</span><span class="o">|</span> <span class="nb">send</span><span class="p">(</span><span class="n">old_name</span><span class="p">,</span> <span class="o">*</span><span class="n">args_munger</span><span class="o">[</span><span class="n">args</span><span class="o">]</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Unlike the Perl, this feels like very natural Ruby to me. You could argue that I struggle with Ruby's syntax because it's not easy to curry a block-taking method, but that seems like a minor issue. (This is fixed in Ruby 1.9, which adds <code>Proc#curry</code>.)</p>
<p>Here are examples of me playing with my toy:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/bin/env ruby -w</span>
<span class="nb">require</span> <span class="s2">"curry"</span>
<span class="n">multiply</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="p">,</span> <span class="n">r</span><span class="o">|</span> <span class="n">l</span> <span class="o">*</span> <span class="n">r</span> <span class="p">}</span>
<span class="n">double</span> <span class="o">=</span> <span class="n">multiply</span><span class="o">.</span><span class="n">curry</span> <span class="p">{</span> <span class="o">|</span><span class="n">args</span><span class="o">|</span> <span class="n">args</span> <span class="o">+</span> <span class="o">[</span><span class="mi">2</span><span class="o">]</span> <span class="p">}</span>
<span class="n">triple</span> <span class="o">=</span> <span class="n">multiply</span><span class="o">.</span><span class="n">curry</span> <span class="p">{</span> <span class="o">|</span><span class="n">args</span><span class="o">|</span> <span class="n">args</span> <span class="o"><<</span> <span class="mi">3</span> <span class="p">}</span>
<span class="n">multiply</span><span class="o">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="o">]</span> <span class="c1"># => 10</span>
<span class="n">double</span><span class="o">[</span><span class="mi">5</span><span class="o">]</span> <span class="c1"># => 10</span>
<span class="n">triple</span><span class="o">[</span><span class="mi">5</span><span class="o">]</span> <span class="c1"># => 15</span>
<span class="n">triple</span><span class="o">[</span><span class="s2">"Howdy "</span><span class="o">]</span> <span class="c1"># => "Howdy Howdy Howdy "</span>
<span class="k">class</span> <span class="nc">Value</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="vi">@value</span> <span class="o">=</span> <span class="n">value</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">*</span><span class="p">(</span><span class="n">other</span><span class="p">)</span>
<span class="vi">@value</span> <span class="o">*</span> <span class="n">other</span>
<span class="k">end</span>
<span class="n">curry</span><span class="p">(</span><span class="ss">:double</span><span class="p">,</span> <span class="ss">:*</span><span class="p">)</span> <span class="p">{</span> <span class="o">[</span><span class="mi">2</span><span class="o">]</span> <span class="p">}</span>
<span class="n">curry</span><span class="p">(</span><span class="ss">:triple</span><span class="p">,</span> <span class="ss">:*</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">args</span><span class="o">|</span> <span class="n">args</span><span class="o">.</span><span class="n">unshift</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">five</span><span class="p">,</span> <span class="n">howdy</span> <span class="o">=</span> <span class="no">Value</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="mi">5</span><span class="p">),</span> <span class="no">Value</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"Howdy "</span><span class="p">)</span>
<span class="n">five</span> <span class="o">*</span> <span class="mi">2</span> <span class="c1"># => 10</span>
<span class="n">five</span><span class="o">.</span><span class="n">double</span> <span class="c1"># => 10</span>
<span class="n">five</span><span class="o">.</span><span class="n">triple</span> <span class="c1"># => 15</span>
<span class="n">howdy</span><span class="o">.</span><span class="n">triple</span> <span class="c1"># => "Howdy Howdy Howdy "</span>
</pre></div>
<p>I am purposefully trying to show the library's flexibility in these calls. The version in the book only supports currying a single argument at the front of the list. In 15 lines the Ruby version can completely rewrite arguments as it sees fit and it includes OO support.</p>
<p>You be the final judge, but I stand by my claim that Higher-Order Perl is the quest to bring many Rubyisms to Perl.</p>
<p>If you want to go deeper down the rabbit hole of currying in Ruby, I recommend you check out the Ruby library <a href="http://rubygems.org/gems/rubymurray">Murray</a>. If you want to see a more Rubyish example of the <code>FlatDB</code> MJD builds in these pages, also see <a href="/ruby-tutorials/code-as-a-data-type">my article on code blocks</a>.</p>James Edward Gray IIInfinite Streamstag:graysoftinc.com,2006-02-20:/posts/92014-03-28T21:17:43ZIn this fifth article of the Higher-Order Ruby series, I show how Ruby's classes can improve on the syntax of MJD's lazy streams.<p>I've tried to summarize this chapter a couple of times now, but I keep getting tripped up over syntax. So, let's talk about that…</p>
<h4>Functional Perl</h4>
<p>Obviously, the examples in the book can be more or less directly translated. Here's a sample from the first couple of pages:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="c1">### Stream Methods ###</span>
<span class="k">def</span> <span class="nf">node</span><span class="p">(</span><span class="n">head</span><span class="p">,</span> <span class="n">tail</span><span class="p">)</span>
<span class="o">[</span><span class="n">head</span><span class="p">,</span> <span class="n">tail</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">head</span><span class="p">(</span><span class="n">stream</span><span class="p">)</span>
<span class="n">stream</span><span class="o">.</span><span class="n">first</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">tail</span><span class="p">(</span><span class="n">stream</span><span class="p">)</span>
<span class="n">tail</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="n">last</span>
<span class="k">if</span> <span class="n">tail</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Proc</span><span class="p">)</span>
<span class="n">tail</span><span class="o">.</span><span class="n">call</span>
<span class="k">else</span>
<span class="n">tail</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">drop</span><span class="p">(</span><span class="n">stream</span><span class="p">)</span>
<span class="n">head</span> <span class="o">=</span> <span class="n">head</span><span class="p">(</span><span class="n">stream</span><span class="p">)</span>
<span class="n">stream</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span><span class="p">,</span> <span class="n">stream</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">(</span><span class="n">tail</span><span class="p">(</span><span class="n">stream</span><span class="p">))</span>
<span class="n">head</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">show</span><span class="p">(</span><span class="n">stream</span><span class="p">,</span> <span class="n">limit</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
<span class="k">while</span> <span class="n">head</span><span class="p">(</span><span class="n">stream</span><span class="p">)</span> <span class="o">&&</span> <span class="p">(</span><span class="n">limit</span><span class="o">.</span><span class="n">nil?</span> <span class="ow">or</span> <span class="p">(</span><span class="n">limit</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">)</span> <span class="o">></span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="nb">print</span> <span class="n">drop</span><span class="p">(</span><span class="n">stream</span><span class="p">),</span> <span class="vg">$,</span> <span class="o">||</span> <span class="s2">" "</span>
<span class="k">end</span>
<span class="nb">print</span> <span class="vg">$/</span>
<span class="k">end</span>
<span class="c1">### Examples ###</span>
<span class="k">def</span> <span class="nf">upto</span><span class="p">(</span><span class="n">from</span><span class="p">,</span> <span class="n">to</span><span class="p">)</span>
<span class="k">return</span> <span class="k">if</span> <span class="n">from</span> <span class="o">></span> <span class="n">to</span>
<span class="n">node</span><span class="p">(</span><span class="n">from</span><span class="p">,</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="n">upto</span><span class="p">(</span><span class="n">from</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">to</span><span class="p">)</span> <span class="p">})</span>
<span class="k">end</span>
<span class="n">show</span><span class="p">(</span><span class="n">upto</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span> <span class="c1"># => 3 4 5 6</span>
<span class="k">def</span> <span class="nf">upfrom</span><span class="p">(</span><span class="n">start</span><span class="p">)</span>
<span class="n">node</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="n">upfrom</span><span class="p">(</span><span class="n">start</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="p">})</span>
<span class="k">end</span>
<span class="n">show</span><span class="p">(</span><span class="n">upfrom</span><span class="p">(</span><span class="mi">7</span><span class="p">),</span> <span class="mi">10</span><span class="p">)</span> <span class="c1"># => 7 8 9 10 11 12 13 14 15 16</span>
</pre></div>
<p>Already though this is feeling very un-Rubyish, and it will only get worse if we keep going down this path.</p>
<p>I suspect the main reason MJD took such a functional approach with his book is that Perl's objects can be heavy and awkward. That doesn't make for great book examples. Ruby isn't like that though and the previous code is just crying out to be objectified.</p>
<h4>Ruby's Object Orientation</h4>
<p>We will start at the beginning. Clearly we need a constructor. Now the whole point of all this is building lazy streams, so we're always going to be using some head value and a promise for the tail. A promise is a chunk of code, and in Ruby we call that a block:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">head</span><span class="p">,</span> <span class="o">&</span><span class="n">promise</span><span class="p">)</span>
<span class="vi">@head</span><span class="p">,</span> <span class="vi">@tail</span> <span class="o">=</span> <span class="n">head</span><span class="p">,</span> <span class="n">promise</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>That's already an improvement, since it will allow us to drop the calls to <code>Kernel#lambda</code>. The name is a bit lengthy, but we can easily fix that after we have the rest of this down. For now, we're just tucking our work into a safe namespace.</p>
<p>The next step is to restore access to the head and tail. These are pretty simple readers in this form and while we are building them, let's add some Rubyish aliases:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="kp">attr_reader</span> <span class="ss">:head</span>
<span class="n">alias_method</span> <span class="ss">:current</span><span class="p">,</span> <span class="ss">:head</span>
<span class="k">def</span> <span class="nf">tail</span>
<span class="k">if</span> <span class="vi">@tail</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Proc</span><span class="p">)</span>
<span class="vi">@tail</span><span class="o">.</span><span class="n">call</span>
<span class="k">else</span>
<span class="vi">@tail</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">alias_method</span> <span class="ss">:next</span><span class="p">,</span> <span class="ss">:tail</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Now, adding back support for drop is where things start to get a little tricky. If a call to <code>LazyStream::Node#tail</code> returns a <code>LazyStream::Node</code>, we want that to replace the current <code>LazyStream::Node</code>. If we get anything else, we'll just assign it normally. Again, here's the code, with aliases:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">drop</span>
<span class="n">result</span><span class="p">,</span> <span class="n">next_stream</span> <span class="o">=</span> <span class="n">head</span><span class="p">,</span> <span class="n">tail</span>
<span class="vi">@head</span><span class="p">,</span> <span class="vi">@tail</span> <span class="o">=</span> <span class="k">if</span> <span class="n">next_stream</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="p">)</span>
<span class="n">next_stream</span><span class="o">.</span><span class="n">instance_eval</span> <span class="p">{</span> <span class="o">[</span><span class="vi">@head</span><span class="p">,</span> <span class="vi">@tail</span><span class="o">]</span> <span class="p">}</span>
<span class="k">else</span>
<span class="nb">Array</span><span class="p">(</span><span class="n">next_stream</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">result</span>
<span class="k">end</span>
<span class="n">alias_method</span> <span class="ss">:tail!</span><span class="p">,</span> <span class="ss">:drop</span>
<span class="n">alias_method</span> <span class="ss">:next!</span><span class="p">,</span> <span class="ss">:drop</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>When using our objectified <code>LazyStream</code>, users won't see a <code>nil</code> return for streams that terminate. Because of that, we should add methods for checking if we are at the end of a stream:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">end?</span>
<span class="vi">@tail</span><span class="o">.</span><span class="n">nil?</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">next?</span>
<span class="o">!</span><span class="k">end</span><span class="p">?</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Let's add a quick <code>LazyStream::Node#show</code> and see how we have done. We will again toss in an alias, an I'm sorry, but the ugly Perl variables have to go:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">show</span><span class="p">(</span><span class="o">*</span><span class="n">limit_and_options</span><span class="p">)</span>
<span class="n">options</span> <span class="o">=</span> <span class="p">{</span><span class="ss">:sep</span> <span class="o">=></span> <span class="s2">" "</span><span class="p">,</span> <span class="ss">:end</span> <span class="o">=></span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">}</span><span class="o">.</span><span class="n">merge!</span><span class="p">(</span>
<span class="n">limit_and_options</span><span class="o">.</span><span class="n">last</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Hash</span><span class="p">)</span> <span class="p">?</span> <span class="n">limit_and_options</span><span class="o">.</span><span class="n">pop</span> <span class="p">:</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span>
<span class="p">)</span>
<span class="n">limit</span> <span class="o">=</span> <span class="n">limit_and_options</span><span class="o">.</span><span class="n">shift</span>
<span class="k">while</span> <span class="n">head</span> <span class="o">&&</span> <span class="p">(</span><span class="n">limit</span><span class="o">.</span><span class="n">nil?</span> <span class="ow">or</span> <span class="p">(</span><span class="n">limit</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">)</span> <span class="o">></span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="nb">print</span> <span class="n">drop</span><span class="p">,</span> <span class="n">options</span><span class="o">[</span><span class="ss">:sep</span><span class="o">]</span>
<span class="k">end</span>
<span class="nb">print</span> <span class="n">options</span><span class="o">[</span><span class="ss">:end</span><span class="o">]</span>
<span class="k">end</span>
<span class="n">alias_method</span> <span class="ss">:display</span><span class="p">,</span> <span class="ss">:show</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>On with the examples:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="nb">require</span> <span class="s2">"lazy_stream"</span>
<span class="k">def</span> <span class="nf">upto</span><span class="p">(</span><span class="n">from</span><span class="p">,</span> <span class="n">to</span><span class="p">)</span>
<span class="k">return</span> <span class="k">if</span> <span class="n">from</span> <span class="o">></span> <span class="n">to</span>
<span class="no">LazyStream</span><span class="o">::</span><span class="no">Node</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">from</span><span class="p">)</span> <span class="p">{</span> <span class="n">upto</span><span class="p">(</span><span class="n">from</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">to</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">upto</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">6</span><span class="p">)</span><span class="o">.</span><span class="n">show</span> <span class="c1"># => 3 4 5 6</span>
<span class="k">def</span> <span class="nf">upfrom</span><span class="p">(</span><span class="n">start</span><span class="p">)</span>
<span class="no">LazyStream</span><span class="o">::</span><span class="no">Node</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">start</span><span class="p">)</span> <span class="p">{</span> <span class="n">upfrom</span><span class="p">(</span><span class="n">start</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">upfrom</span><span class="p">(</span><span class="mi">7</span><span class="p">)</span><span class="o">.</span><span class="n">show</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="c1"># => 7 8 9 10 11 12 13 14 15 16</span>
</pre></div>
<p>We're making progress. This is starting to feel more like Ruby. Time for me to make good on my promise for a shorter constructor though. We can just add this to the end of lazy_stream.rb:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">Kernel</span>
<span class="k">def</span> <span class="nf">lazy_stream</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="no">LazyStream</span><span class="o">::</span><span class="no">Node</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>That allows us to trim our functional usage to:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="nb">require</span> <span class="s2">"lazy_stream"</span>
<span class="k">def</span> <span class="nf">upto</span><span class="p">(</span><span class="n">from</span><span class="p">,</span> <span class="n">to</span><span class="p">)</span>
<span class="k">return</span> <span class="k">if</span> <span class="n">from</span> <span class="o">></span> <span class="n">to</span>
<span class="n">lazy_stream</span><span class="p">(</span><span class="n">from</span><span class="p">)</span> <span class="p">{</span> <span class="n">upto</span><span class="p">(</span><span class="n">from</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">to</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">upto</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">6</span><span class="p">)</span><span class="o">.</span><span class="n">show</span> <span class="c1"># => 3 4 5 6</span>
<span class="k">def</span> <span class="nf">upfrom</span><span class="p">(</span><span class="n">start</span><span class="p">)</span>
<span class="n">lazy_stream</span><span class="p">(</span><span class="n">start</span><span class="p">)</span> <span class="p">{</span> <span class="n">upfrom</span><span class="p">(</span><span class="n">start</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">upfrom</span><span class="p">(</span><span class="mi">7</span><span class="p">)</span><span class="o">.</span><span class="n">show</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="c1"># => 7 8 9 10 11 12 13 14 15 16</span>
</pre></div>
<p>Even better though, this is Ruby and we can make full objects:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="nb">require</span> <span class="s2">"lazy_stream"</span>
<span class="k">class</span> <span class="nc">Upto</span> <span class="o"><</span> <span class="no">LazyStream</span><span class="o">::</span><span class="no">Node</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">from</span><span class="p">,</span> <span class="n">to</span><span class="p">)</span>
<span class="k">if</span> <span class="n">from</span> <span class="o">></span> <span class="n">to</span>
<span class="k">super</span><span class="p">(</span><span class="kp">nil</span><span class="p">,</span> <span class="o">&</span><span class="kp">nil</span><span class="p">)</span>
<span class="k">else</span>
<span class="k">super</span><span class="p">(</span><span class="n">from</span><span class="p">)</span> <span class="p">{</span> <span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">from</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">to</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="no">Upto</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">6</span><span class="p">)</span><span class="o">.</span><span class="n">show</span> <span class="c1"># => 3 4 5 6</span>
<span class="k">class</span> <span class="nc">Upfrom</span> <span class="o"><</span> <span class="no">LazyStream</span><span class="o">::</span><span class="no">Node</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">from</span><span class="p">)</span>
<span class="k">super</span><span class="p">(</span><span class="n">from</span><span class="p">)</span> <span class="p">{</span> <span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">from</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="no">Upfrom</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="mi">7</span><span class="p">)</span><span class="o">.</span><span class="n">show</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="c1"># => 7 8 9 10 11 12 13 14 15 16</span>
</pre></div>
<p>Now we can have our interface any way we like it. Some things still need work though…</p>
<h4>Inside-out Iteration</h4>
<p>So far, I've just copied MJD's external iterator interface. Those are certainly helpful with lazy streams, because you may want to pull values for a while, stop to do something else, and come back to the stream later.</p>
<p>However, Ruby just doesn't feel right without an <code>#each</code> method and <code>Enumerable</code> mixed-in. In this particular case, we really want two forms of iteration, one each for <code>LazyStream::Node#tail</code> and <code>LazyStream::Node#drop</code> so we can advance the stream, or just peek ahead.</p>
<p>It's easier to build the destructive version first. In fact, we already have the implementation coded up inside <code>LazyStream::Node#show</code>. Let's even keep the limit as a handy way to keep from going too deep:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">each!</span><span class="p">(</span><span class="n">limit</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="k">break</span> <span class="k">unless</span> <span class="n">limit</span><span class="o">.</span><span class="n">nil?</span> <span class="o">||</span> <span class="p">(</span><span class="n">limit</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">)</span> <span class="o">></span> <span class="o">-</span><span class="mi">1</span>
<span class="k">yield</span><span class="p">(</span><span class="n">drop</span><span class="p">)</span>
<span class="k">break</span> <span class="k">if</span> <span class="k">end</span><span class="p">?</span>
<span class="k">end</span>
<span class="nb">self</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Then we can use that to build the standard iterator:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">each</span><span class="p">(</span><span class="n">limit</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="nb">clone</span><span class="o">.</span><span class="n">each!</span><span class="p">(</span><span class="n">limit</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="nb">self</span>
<span class="k">end</span>
<span class="n">alias_method</span> <span class="ss">:peek</span><span class="p">,</span> <span class="ss">:each</span>
<span class="kp">include</span> <span class="no">Enumerable</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>That let's us use standard Ruby iteration, for the most part. We can even set a limit to keep from going too far. We can also use methods like <code>Enumerable#find</code> on an infinite stream, since it will stop as soon as it finds a match.</p>
<p>Where this system has trouble is if we want to use something like <code>Enumerable#map</code> on an infinite stream. The problem here is that we have no good way to pass the limit down. It's also tricky to use <code>LazyStream::Node#each!</code> with the <code>Enumerable</code> methods. Let's fix both issues:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"enumerator"</span>
<span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">limit</span><span class="p">(</span><span class="n">max_depth</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
<span class="n">enum_for</span><span class="p">(</span><span class="ss">:each</span><span class="p">,</span> <span class="n">max_depth</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">limit!</span><span class="p">(</span><span class="n">max_depth</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
<span class="n">enum_for</span><span class="p">(</span><span class="ss">:each!</span><span class="p">,</span> <span class="n">max_depth</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>There's a little black magic there we need to see in action, but before we go back to examples we can simplify <code>LazyStream::Node#show</code> to use the new iterators:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">show</span><span class="p">(</span><span class="o">*</span><span class="n">limit_and_options</span><span class="p">)</span>
<span class="n">options</span> <span class="o">=</span> <span class="p">{</span><span class="ss">:sep</span> <span class="o">=></span> <span class="s2">" "</span><span class="p">,</span> <span class="ss">:end</span> <span class="o">=></span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">}</span><span class="o">.</span><span class="n">merge!</span><span class="p">(</span>
<span class="n">limit_and_options</span><span class="o">.</span><span class="n">last</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Hash</span><span class="p">)</span> <span class="p">?</span> <span class="n">limit_and_options</span><span class="o">.</span><span class="n">pop</span> <span class="p">:</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span>
<span class="p">)</span>
<span class="n">limit</span> <span class="o">=</span> <span class="n">limit_and_options</span><span class="o">.</span><span class="n">shift</span>
<span class="n">each</span><span class="p">(</span><span class="n">limit</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">cur</span><span class="o">|</span> <span class="nb">print</span> <span class="n">cur</span><span class="p">,</span> <span class="n">options</span><span class="o">[</span><span class="ss">:sep</span><span class="o">]</span> <span class="p">}</span>
<span class="nb">print</span> <span class="n">options</span><span class="o">[</span><span class="ss">:end</span><span class="o">]</span>
<span class="k">end</span>
<span class="n">alias_method</span> <span class="ss">:display</span><span class="p">,</span> <span class="ss">:show</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Okay, let's see what we have created. Here's an infinite stream iterator, using <code>Enumerable#map</code>:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="nb">require</span> <span class="s2">"lazy_stream"</span>
<span class="k">class</span> <span class="nc">Step</span> <span class="o"><</span> <span class="no">LazyStream</span><span class="o">::</span><span class="no">Node</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">step</span><span class="p">,</span> <span class="n">start</span> <span class="o">=</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">super</span><span class="p">(</span><span class="n">start</span><span class="p">)</span> <span class="p">{</span> <span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">step</span><span class="p">,</span> <span class="n">start</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="p">}</span>
<span class="vi">@step</span> <span class="o">=</span> <span class="n">step</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">next_group</span><span class="p">(</span><span class="n">count</span> <span class="o">=</span> <span class="mi">10</span><span class="p">)</span>
<span class="n">limit!</span><span class="p">(</span><span class="n">count</span><span class="p">)</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="n">i</span> <span class="o">*</span> <span class="vi">@step</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">evens</span> <span class="o">=</span> <span class="no">Step</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s2">"The first ten even numbers are:"</span>
<span class="nb">puts</span> <span class="n">evens</span><span class="o">.</span><span class="n">next_group</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span> <span class="c1"># => 2 4 6 8 10 12 14 16 18 20</span>
<span class="c1"># later...</span>
<span class="nb">puts</span>
<span class="nb">puts</span> <span class="s2">"The next ten even numbers are:"</span>
<span class="nb">puts</span> <span class="n">evens</span><span class="o">.</span><span class="n">next_group</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span> <span class="c1"># => 22 24 26 28 30 32 34 36 38 40</span>
<span class="nb">puts</span>
<span class="nb">puts</span> <span class="s2">"The current index for future calculations is:"</span>
<span class="nb">puts</span> <span class="n">evens</span><span class="o">.</span><span class="n">current</span> <span class="c1"># => 21</span>
</pre></div>
<p>That feels a lot more like Ruby to me. Now we can get back to MJD's examples.</p>
<h4>Adding <code>#filter</code> and <code>#transform</code>
</h4>
<p>MJD adds both a <code>#filter</code> and <code>#transform</code> method next. Technically, my last example is a transformation of the stream and we could do filtering the same way. Still it's nice to have these built-in and we should add them.</p>
<p>The <code>#filter</code> is easy enough, we just have to keep dropping items until we find a match and make sure setting a filter is viral to future nodes:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">tail</span>
<span class="n">result</span> <span class="o">=</span> <span class="k">if</span> <span class="vi">@tail</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Proc</span><span class="p">)</span>
<span class="vi">@tail</span><span class="o">.</span><span class="n">call</span>
<span class="k">else</span>
<span class="vi">@tail</span>
<span class="k">end</span>
<span class="n">result</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="vi">@filter</span><span class="p">)</span> <span class="k">unless</span> <span class="vi">@filter</span><span class="o">.</span><span class="n">nil?</span> <span class="o">||</span> <span class="o">!</span><span class="n">result</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="p">)</span>
<span class="n">result</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">filter</span><span class="p">(</span><span class="n">pattern</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="vi">@filter</span> <span class="o">=</span> <span class="n">pattern</span> <span class="o">||</span> <span class="n">block</span>
<span class="n">drop</span> <span class="k">until</span> <span class="n">matches_filter?</span><span class="p">(</span><span class="vi">@head</span><span class="p">)</span>
<span class="nb">self</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">matches_filter?</span><span class="p">(</span><span class="n">current</span><span class="p">)</span>
<span class="k">case</span> <span class="vi">@filter</span>
<span class="k">when</span> <span class="kp">nil</span>
<span class="kp">true</span>
<span class="k">when</span> <span class="no">Proc</span>
<span class="vi">@filter</span><span class="o">[</span><span class="n">current</span><span class="o">]</span>
<span class="k">else</span>
<span class="vi">@filter</span> <span class="o">===</span> <span class="n">current</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>We can add <code>#transform</code> by defining an actual getter for <code>@head</code>, instead of the <code>attr_reader</code> shortcut I've been using and, again, making the setting viral:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">head</span>
<span class="vi">@transformer</span><span class="o">.</span><span class="n">nil?</span> <span class="p">?</span> <span class="vi">@head</span> <span class="p">:</span> <span class="vi">@transformer</span><span class="o">[</span><span class="vi">@head</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">tail</span>
<span class="n">result</span> <span class="o">=</span> <span class="k">if</span> <span class="vi">@tail</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Proc</span><span class="p">)</span>
<span class="vi">@tail</span><span class="o">.</span><span class="n">call</span>
<span class="k">else</span>
<span class="vi">@tail</span>
<span class="k">end</span>
<span class="n">result</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="vi">@filter</span><span class="p">)</span> <span class="k">unless</span> <span class="vi">@filter</span><span class="o">.</span><span class="n">nil?</span> <span class="o">||</span>
<span class="o">!</span><span class="n">result</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="p">)</span>
<span class="n">result</span><span class="o">.</span><span class="n">transform</span><span class="p">(</span><span class="o">&</span><span class="vi">@transformer</span><span class="p">)</span> <span class="k">unless</span> <span class="vi">@transformer</span><span class="o">.</span><span class="n">nil?</span> <span class="o">||</span>
<span class="o">!</span><span class="n">result</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="p">)</span>
<span class="n">result</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">transform</span><span class="p">(</span><span class="o">&</span><span class="n">transformer</span><span class="p">)</span>
<span class="vi">@transformer</span> <span class="o">=</span> <span class="n">transformer</span>
<span class="nb">self</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Here are trivial examples with both:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="nb">require</span> <span class="s2">"lazy_stream"</span>
<span class="k">def</span> <span class="nf">letters</span><span class="p">(</span><span class="n">letter</span><span class="p">)</span>
<span class="n">lazy_stream</span><span class="p">(</span><span class="n">letter</span><span class="p">)</span> <span class="p">{</span> <span class="n">letters</span><span class="p">(</span><span class="n">letter</span><span class="o">.</span><span class="n">succ</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">letters</span><span class="p">(</span><span class="s2">"a"</span><span class="p">)</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="sr">/[aeiou]/</span><span class="p">)</span><span class="o">.</span><span class="n">show</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="c1"># => a e i o u aa ab ac ad ae</span>
<span class="n">letters</span><span class="p">(</span><span class="s2">"a"</span><span class="p">)</span><span class="o">.</span><span class="n">filter</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="o">|</span> <span class="n">l</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">2</span> <span class="p">}</span><span class="o">.</span><span class="n">show</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="c1"># => aa ab ac </span>
<span class="n">letters</span><span class="p">(</span><span class="s2">"a"</span><span class="p">)</span><span class="o">.</span><span class="n">transform</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="o">|</span> <span class="n">l</span> <span class="o">+</span> <span class="s2">"..."</span> <span class="p">}</span><span class="o">.</span><span class="n">show</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="c1"># => a... b... c...</span>
</pre></div>
<h4>Recursive Streams</h4>
<p>I think we can fix the recursive issues MJD discusses with ease in our Ruby version, just by passing the stream head to the promise block:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">tail</span>
<span class="n">result</span> <span class="o">=</span> <span class="k">if</span> <span class="vi">@tail</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Proc</span><span class="p">)</span>
<span class="vi">@tail</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">head</span><span class="p">)</span>
<span class="k">else</span>
<span class="vi">@tail</span>
<span class="k">end</span>
<span class="k">unless</span> <span class="n">result</span> <span class="o">==</span> <span class="vi">@tail</span>
<span class="n">result</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="vi">@filter</span><span class="p">)</span> <span class="k">unless</span> <span class="vi">@filter</span><span class="o">.</span><span class="n">nil?</span> <span class="o">||</span>
<span class="o">!</span><span class="n">result</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="p">)</span>
<span class="n">result</span><span class="o">.</span><span class="n">transform</span><span class="p">(</span><span class="o">&</span><span class="vi">@transformer</span><span class="p">)</span> <span class="k">unless</span> <span class="vi">@transformer</span><span class="o">.</span><span class="n">nil?</span> <span class="o">||</span>
<span class="o">!</span><span class="n">result</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="p">)</span>
<span class="k">end</span>
<span class="vi">@tail</span> <span class="o">=</span> <span class="n">result</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>With that, MJD's example is trivial:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="nb">require</span> <span class="s2">"lazy_stream"</span>
<span class="k">class</span> <span class="nc">Powers</span> <span class="o"><</span> <span class="no">LazyStream</span><span class="o">::</span><span class="no">Node</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">of</span><span class="p">,</span> <span class="n">start</span> <span class="o">=</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">super</span><span class="p">(</span><span class="n">start</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">last</span><span class="o">|</span> <span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">of</span><span class="p">,</span> <span class="n">last</span> <span class="o">*</span> <span class="n">of</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">powers_of_two</span> <span class="o">=</span> <span class="no">Powers</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="n">powers_of_two</span><span class="o">.</span><span class="n">show</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="c1"># => 1 2 4 8 16 32 64 128 256 512</span>
</pre></div>
<p>In all honesty though, you seldom need that with the blocks being closures. You can just reference the local variables instead.</p>
<p>You also don't need merging to solve the Hamming Sequence. Here's a simple example:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="nb">require</span> <span class="s2">"lazy_stream"</span>
<span class="k">class</span> <span class="nc">Hamming</span> <span class="o"><</span> <span class="no">LazyStream</span><span class="o">::</span><span class="no">Node</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">seq</span> <span class="o">=</span> <span class="o">[</span><span class="mi">1</span><span class="o">]</span><span class="p">)</span>
<span class="n">cur</span> <span class="o">=</span> <span class="n">seq</span><span class="o">.</span><span class="n">shift</span>
<span class="k">super</span><span class="p">(</span><span class="n">cur</span><span class="p">)</span> <span class="k">do</span>
<span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">new</span><span class="p">((</span><span class="n">seq</span> <span class="o"><<</span> <span class="n">cur</span> <span class="o">*</span> <span class="mi">2</span> <span class="o"><<</span> <span class="n">cur</span> <span class="o">*</span> <span class="mi">3</span> <span class="o"><<</span> <span class="n">cur</span> <span class="o">*</span> <span class="mi">5</span><span class="p">)</span><span class="o">.</span><span class="n">uniq</span><span class="o">.</span><span class="n">sort</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="no">Hamming</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">show</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="c1"># => 1 2 3 4 5 6 8 9 10 12 15 16 18 20 24</span>
</pre></div>
<h4>Regexp String Generation</h4>
<p>We don't need too many modifications to be able to create this example. First, let's build a <code>LazyStream::union</code>:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">union</span><span class="p">(</span><span class="o">*</span><span class="n">streams</span><span class="p">)</span>
<span class="n">current</span> <span class="o">=</span> <span class="n">streams</span><span class="o">.</span><span class="n">shift</span> <span class="ow">or</span> <span class="k">return</span>
<span class="no">Node</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">current</span><span class="o">.</span><span class="n">head</span><span class="p">)</span> <span class="p">{</span> <span class="n">union</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">streams</span> <span class="o"><<</span> <span class="n">current</span><span class="o">.</span><span class="n">tail</span><span class="p">))</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>We can even add a little syntax sugar for calling that:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">+</span><span class="p">(</span><span class="n">other_stream</span><span class="p">)</span>
<span class="no">LazyStream</span><span class="o">.</span><span class="n">union</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="n">other_stream</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Finally, because I made <code>LazyStream::Node#transform</code> modify the stream, we will need a <code>LazyStream::Node#dup</code> to build MJD's <code>#concat</code> method:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">LazyStream</span>
<span class="k">class</span> <span class="nc">Node</span>
<span class="k">def</span> <span class="nf">dup</span>
<span class="k">if</span> <span class="n">tail</span><span class="o">.</span><span class="n">nil?</span> <span class="o">||</span> <span class="n">tail</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Proc</span><span class="p">)</span>
<span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">head</span><span class="p">,</span> <span class="o">&</span><span class="n">tail</span><span class="p">)</span>
<span class="k">else</span>
<span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">head</span><span class="p">)</span> <span class="p">{</span> <span class="n">tail</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>From there we can build a <code>RegexpMatchesGenerator</code>:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="nb">require</span> <span class="s2">"lazy_stream"</span>
<span class="k">module</span> <span class="nn">RegexpMatchesGenerator</span>
<span class="kp">extend</span> <span class="nb">self</span>
<span class="k">def</span> <span class="nf">literal</span><span class="p">(</span><span class="n">string</span><span class="p">)</span>
<span class="n">lazy_stream</span><span class="p">(</span><span class="n">string</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">concat</span><span class="p">(</span><span class="n">stream1</span><span class="p">,</span> <span class="n">stream2</span><span class="p">)</span>
<span class="n">lazy_stream</span><span class="p">(</span><span class="n">stream1</span><span class="o">.</span><span class="n">head</span> <span class="o">+</span> <span class="n">stream2</span><span class="o">.</span><span class="n">head</span><span class="p">)</span> <span class="k">do</span>
<span class="n">combinations</span> <span class="o">=</span> <span class="nb">Array</span><span class="o">.</span><span class="n">new</span>
<span class="k">unless</span> <span class="n">stream1</span><span class="o">.</span><span class="n">end?</span>
<span class="n">combinations</span> <span class="o"><<</span> <span class="n">stream1</span><span class="o">.</span><span class="n">tail</span><span class="o">.</span><span class="n">dup</span><span class="o">.</span><span class="n">transform</span> <span class="p">{</span> <span class="o">|</span><span class="n">str</span><span class="o">|</span> <span class="n">str</span> <span class="o">+</span> <span class="n">stream2</span><span class="o">.</span><span class="n">head</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">unless</span> <span class="n">stream2</span><span class="o">.</span><span class="n">end?</span>
<span class="n">combinations</span> <span class="o"><<</span> <span class="n">stream2</span><span class="o">.</span><span class="n">tail</span><span class="o">.</span><span class="n">dup</span><span class="o">.</span><span class="n">transform</span> <span class="p">{</span> <span class="o">|</span><span class="n">str</span><span class="o">|</span> <span class="n">stream1</span><span class="o">.</span><span class="n">head</span> <span class="o">+</span> <span class="n">str</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">unless</span> <span class="n">stream1</span><span class="o">.</span><span class="n">end?</span> <span class="o">||</span> <span class="n">stream2</span><span class="o">.</span><span class="n">end?</span>
<span class="n">combinations</span> <span class="o"><<</span> <span class="n">concat</span><span class="p">(</span><span class="n">stream1</span><span class="o">.</span><span class="n">tail</span><span class="p">,</span> <span class="n">stream2</span><span class="o">.</span><span class="n">tail</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">LazyStream</span><span class="o">.</span><span class="n">union</span><span class="p">(</span><span class="o">*</span><span class="n">combinations</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">star</span><span class="p">(</span><span class="n">stream</span><span class="p">,</span> <span class="n">head</span> <span class="o">=</span> <span class="s2">""</span><span class="p">)</span>
<span class="n">lazy_stream</span><span class="p">(</span><span class="n">head</span><span class="p">)</span> <span class="p">{</span> <span class="n">star</span><span class="p">(</span><span class="n">stream</span><span class="p">,</span> <span class="n">head</span> <span class="o">+</span> <span class="n">stream</span><span class="o">.</span><span class="n">head</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">char_class</span><span class="p">(</span><span class="n">string</span><span class="p">)</span>
<span class="no">LazyStream</span><span class="o">.</span><span class="n">union</span><span class="p">(</span><span class="o">*</span><span class="n">string</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">str</span><span class="o">|</span> <span class="n">literal</span><span class="p">(</span><span class="n">str</span><span class="p">)</span> <span class="p">})</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">plus</span><span class="p">(</span><span class="n">stream</span><span class="p">)</span>
<span class="n">star</span><span class="p">(</span><span class="n">stream</span><span class="p">,</span> <span class="n">stream</span><span class="o">.</span><span class="n">head</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">if</span> <span class="bp">__FILE__</span> <span class="o">==</span> <span class="vg">$0</span>
<span class="kp">include</span> <span class="no">RegexpMatchesGenerator</span>
<span class="c1"># /^(a|b)(c|d)$/</span>
<span class="n">concat</span><span class="p">(</span><span class="n">literal</span><span class="p">(</span><span class="s2">"a"</span><span class="p">)</span> <span class="o">+</span> <span class="n">literal</span><span class="p">(</span><span class="s2">"b"</span><span class="p">),</span> <span class="n">literal</span><span class="p">(</span><span class="s2">"c"</span><span class="p">)</span> <span class="o">+</span> <span class="n">literal</span><span class="p">(</span><span class="s2">"d"</span><span class="p">))</span><span class="o">.</span><span class="n">show</span>
<span class="c1"># /^(HONK)*$/</span>
<span class="nb">puts</span> <span class="n">star</span><span class="p">(</span><span class="n">literal</span><span class="p">(</span><span class="s2">"HONK"</span><span class="p">))</span><span class="o">.</span><span class="n">limit</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">str</span><span class="o">|</span> <span class="n">str</span><span class="o">.</span><span class="n">inspect</span> <span class="p">}</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span>
<span class="c1"># /^ab*$/</span>
<span class="nb">puts</span><span class="p">(</span><span class="n">concat</span><span class="p">(</span><span class="n">literal</span><span class="p">(</span><span class="s2">"a"</span><span class="p">),</span> <span class="n">star</span><span class="p">(</span><span class="n">literal</span><span class="p">(</span><span class="s2">"b"</span><span class="p">)))</span><span class="o">.</span><span class="n">limit</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span><span class="o">.</span><span class="n">to_a</span><span class="p">)</span>
<span class="k">end</span>
</pre></div>
<h4>The Rest</h4>
<p>That should be enough example translation to address most of the highlights from this chapter. All those math problems at the end make me drowsy, so I'll leave those as an exercise for the reader…</p>James Edward Gray IIIterators (Chapters 4 and 5)tag:graysoftinc.com,2006-01-31:/posts/82016-09-08T12:15:21ZIn this fourth article of the Higher-Order Ruby series, we get to take a look at a Ruby specialty: iterators.<p>Due to a printing error, these two chapters actually came out longer than intended. Originally their contents were: "Use Ruby."</p>
<p>All jokes aside, there's really not a whole lot for me to talk about from these chapters, since iterators are so internal to Ruby. Readers from our camp should run into a lot less surprises here that the intended audience. Just translate MDJ's anonymous subroutines to blocks, replace his returns with yields, and you are 90% of the way there.</p>
<p>Here are translations for some of the examples in these chapters. I think these all come out cleaner and more natural in Ruby, but you be the judge:</p>
<h4>Permutations</h4>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="k">def</span> <span class="nf">permute</span><span class="p">(</span><span class="n">items</span><span class="p">)</span>
<span class="mi">0</span><span class="o">.</span><span class="n">upto</span><span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="mi">0</span><span class="o">/</span><span class="mi">0</span><span class="o">.</span><span class="mi">0</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">count</span><span class="o">|</span>
<span class="n">pattern</span> <span class="o">=</span> <span class="n">count_to_pattern</span><span class="p">(</span><span class="n">count</span><span class="p">,</span> <span class="n">items</span><span class="o">.</span><span class="n">size</span><span class="p">)</span> <span class="ow">or</span> <span class="k">break</span>
<span class="nb">puts</span> <span class="s2">"Pattern </span><span class="si">#{</span><span class="n">pattern</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s1">' '</span><span class="p">)</span><span class="si">}</span><span class="s2">:"</span> <span class="k">if</span> <span class="vg">$DEBUG</span>
<span class="k">yield</span><span class="p">(</span><span class="n">pattern_to_permutation</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">items</span><span class="o">.</span><span class="n">dup</span><span class="p">))</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">pattern_to_permutation</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">items</span><span class="p">)</span>
<span class="n">pattern</span><span class="o">.</span><span class="n">inject</span><span class="p">(</span><span class="nb">Array</span><span class="o">.</span><span class="n">new</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">results</span><span class="p">,</span> <span class="n">i</span><span class="o">|</span> <span class="n">results</span> <span class="o">+</span> <span class="n">items</span><span class="o">.</span><span class="n">slice!</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">count_to_pattern</span><span class="p">(</span><span class="n">count</span><span class="p">,</span> <span class="n">item_count</span><span class="p">)</span>
<span class="n">pattern</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="n">.item_count</span><span class="p">)</span><span class="o">.</span><span class="n">inject</span><span class="p">(</span><span class="nb">Array</span><span class="o">.</span><span class="n">new</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">pat</span><span class="p">,</span> <span class="n">i</span><span class="o">|</span>
<span class="n">pat</span><span class="o">.</span><span class="n">unshift</span><span class="p">(</span><span class="n">count</span> <span class="o">%</span> <span class="n">i</span><span class="p">)</span>
<span class="n">count</span> <span class="o">/=</span> <span class="n">i</span>
<span class="n">pat</span>
<span class="k">end</span>
<span class="n">count</span><span class="o">.</span><span class="n">zero?</span> <span class="p">?</span> <span class="n">pattern</span> <span class="p">:</span> <span class="kp">nil</span>
<span class="k">end</span>
<span class="k">if</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">empty?</span>
<span class="nb">abort</span> <span class="s2">"Usage: </span><span class="si">#{</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vg">$PROGRAM_NAME</span><span class="p">)</span><span class="si">}</span><span class="s2"> LIST_OF_ITEMS"</span>
<span class="k">end</span>
<span class="n">permute</span><span class="p">(</span><span class="no">ARGV</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">perm</span><span class="o">|</span> <span class="nb">puts</span><span class="p">((</span><span class="vg">$DEBUG</span> <span class="p">?</span> <span class="s2">" "</span> <span class="p">:</span> <span class="s2">""</span><span class="p">)</span> <span class="o">+</span> <span class="n">perm</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">" "</span><span class="p">))</span> <span class="p">}</span>
</pre></div>
<h4>Treasure Allocation</h4>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="k">def</span> <span class="nf">find_shares</span><span class="p">(</span><span class="n">target</span><span class="p">,</span> <span class="n">treasures</span><span class="p">)</span>
<span class="n">shares</span> <span class="o">=</span> <span class="n">target</span><span class="o">.</span><span class="n">zero?</span> <span class="p">?</span> <span class="o">[[</span><span class="n">target</span><span class="p">,</span> <span class="nb">Array</span><span class="o">.</span><span class="n">new</span><span class="p">,</span> <span class="nb">Array</span><span class="o">.</span><span class="n">new</span><span class="o">]]</span> <span class="p">:</span>
<span class="o">[[</span><span class="n">target</span><span class="p">,</span> <span class="n">treasures</span><span class="p">,</span> <span class="nb">Array</span><span class="o">.</span><span class="n">new</span><span class="o">]]</span>
<span class="k">until</span> <span class="n">shares</span><span class="o">.</span><span class="n">empty?</span>
<span class="n">goal</span><span class="p">,</span> <span class="n">pool</span><span class="p">,</span> <span class="n">share</span> <span class="o">=</span> <span class="n">shares</span><span class="o">.</span><span class="n">pop</span>
<span class="n">first</span> <span class="o">=</span> <span class="n">pool</span><span class="o">.</span><span class="n">shift</span>
<span class="n">shares</span> <span class="o"><<</span> <span class="o">[</span><span class="n">goal</span><span class="p">,</span> <span class="n">pool</span><span class="o">.</span><span class="n">dup</span><span class="p">,</span> <span class="n">share</span><span class="o">.</span><span class="n">dup</span><span class="o">]</span> <span class="k">unless</span> <span class="n">pool</span><span class="o">.</span><span class="n">empty?</span>
<span class="k">if</span> <span class="n">goal</span> <span class="o">==</span> <span class="n">first</span>
<span class="k">yield</span><span class="p">(</span><span class="n">share</span> <span class="o">+</span> <span class="o">[</span><span class="n">first</span><span class="o">]</span><span class="p">)</span>
<span class="k">elsif</span> <span class="n">goal</span> <span class="o">></span> <span class="n">first</span> <span class="o">&&</span> <span class="o">!</span><span class="n">pool</span><span class="o">.</span><span class="n">empty?</span>
<span class="n">shares</span> <span class="o"><<</span> <span class="o">[</span><span class="n">goal</span> <span class="o">-</span> <span class="n">first</span><span class="p">,</span> <span class="n">pool</span><span class="o">.</span><span class="n">dup</span><span class="p">,</span> <span class="n">share</span> <span class="o">+</span> <span class="o">[</span><span class="n">first</span><span class="o">]]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">unless</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">size</span> <span class="o">>=</span> <span class="mi">2</span>
<span class="nb">abort</span> <span class="s2">"Usage: </span><span class="si">#{</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vg">$PROGRAM_NAME</span><span class="p">)</span><span class="si">}</span><span class="s2"> TARGET TREASURES"</span>
<span class="k">end</span>
<span class="n">find_shares</span><span class="p">(</span><span class="no">ARGV</span><span class="o">.</span><span class="n">first</span><span class="o">.</span><span class="n">to_i</span><span class="p">,</span> <span class="no">ARGV</span><span class="o">[</span><span class="mi">1</span><span class="o">.</span><span class="n">.</span><span class="o">-</span><span class="mi">1</span><span class="o">].</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span> <span class="n">n</span><span class="o">.</span><span class="n">to_i</span> <span class="p">})</span> <span class="k">do</span> <span class="o">|</span><span class="n">share</span><span class="o">|</span>
<span class="nb">puts</span> <span class="n">share</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span>
<span class="k">end</span>
</pre></div>
<h4>Integer Partitioning</h4>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="k">def</span> <span class="nf">partition</span><span class="p">(</span><span class="n">num</span><span class="p">)</span>
<span class="n">partitions</span> <span class="o">=</span> <span class="o">[[</span><span class="n">num</span><span class="o">]]</span>
<span class="k">until</span> <span class="n">partitions</span><span class="o">.</span><span class="n">empty?</span>
<span class="n">current</span> <span class="o">=</span> <span class="n">partitions</span><span class="o">.</span><span class="n">shift</span>
<span class="k">yield</span><span class="p">(</span><span class="n">current</span><span class="p">)</span>
<span class="n">largest</span> <span class="o">=</span> <span class="n">current</span><span class="o">.</span><span class="n">shift</span>
<span class="p">((</span><span class="o">[</span><span class="n">current</span><span class="o">.</span><span class="n">first</span><span class="o">.</span><span class="n">to_i</span><span class="p">,</span> <span class="mi">1</span><span class="o">].</span><span class="n">max</span><span class="p">)</span><span class="o">.</span><span class="n">.</span><span class="p">(</span><span class="n">largest</span> <span class="o">/</span> <span class="mi">2</span><span class="p">))</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span>
<span class="n">partitions</span> <span class="o"><<</span> <span class="nb">Array</span><span class="o">[</span><span class="n">largest</span> <span class="o">-</span> <span class="n">n</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="o">*</span><span class="n">current</span><span class="o">.</span><span class="n">dup</span><span class="o">]</span>
<span class="k">end</span>
<span class="n">partitions</span><span class="o">.</span><span class="n">sort!</span> <span class="p">{</span> <span class="o">|</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="o">|</span> <span class="n">b</span> <span class="o"><=></span> <span class="n">a</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">unless</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">1</span>
<span class="nb">abort</span> <span class="s2">"Usage: </span><span class="si">#{</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vg">$PROGRAM_NAME</span><span class="p">)</span><span class="si">}</span><span class="s2"> INTEGER"</span>
<span class="k">end</span>
<span class="n">partition</span><span class="p">(</span><span class="no">ARGV</span><span class="o">.</span><span class="n">first</span><span class="o">.</span><span class="n">to_i</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">partition</span><span class="o">|</span> <span class="nb">puts</span> <span class="n">partition</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span> <span class="p">}</span>
</pre></div>
<h4>Internal vs. External</h4>
<p>All of the the iterators MJD uses are "external iterators." That's just a fancy way of saying that he returns these magic objects you can query for each item in a series. When you are done with an item, you can just ask for the next one, and it will let you know when it runs out of items to give you. (Java's iterators are also external, if you are familiar with that language.)</p>
<p>Ruby uses a different approach, called "internal iterators." Instead of asking for the magic object (pulling data), we pass in the operations to do on the items we are iterating over (pushing data) in the form of blocks.</p>
<p>Both models have their advantages.</p>
<p>First think about this: Ruby's iterators don't suffer from the semipredicate issues MJD keeps describing in these pages. You never have to worry about whether or not <code>each()</code> is trying to tell you it's out of items, because it handles all of that. That's an advantage of internal iterators.</p>
<p>However, sometimes we need to work through a couple of iterators in tandem. Internal iterators struggle with this, because we hand the processing code off for another object to run and manage. In these cases, external iterators fair better.</p>
<p>You should remember two things from this.</p>
<p>First, most of the time there is little difference and in the typical cases and when that's true internal iterators tend to come out cleaner, in my opinion. See the three examples above.</p>
<p>Second, some problems just need an external iterator. Because of that, you need to know how to get one in Ruby. There are a couple of ways, but tuck this one away in your mind because it's so easy to remember: all <code>Enumerable</code> objects have a <code>to_a()</code> method and an <code>Array</code> can be an external iterator (using indices).</p>
<p>MJD had a great example of a problem that doesn't bend well to internal iterators in this section, when he was playing with gene combinations. He solved it like I just hinted at, using arrays and tracking indices. Here's another option in Ruby using the standard <code>Generator</code> library, which is for switching internal iterators into external iterators:</p>
<p><em>[<strong>Note</strong>: The following code uses a standard library that has been removed from Ruby. It's no longer needed now that any iterator is trivially switched into an <code>Enumerator</code>, Ruby's current system for getting external iterators.]</em></p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="nb">require</span> <span class="s2">"generator"</span>
<span class="k">def</span> <span class="nf">permute_genes</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span>
<span class="n">tokens</span> <span class="o">=</span> <span class="n">pattern</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="sr">/[()]/</span><span class="p">)</span>
<span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="n">.</span><span class="o">.</span><span class="n">tokens</span><span class="o">.</span><span class="n">size</span><span class="p">)</span><span class="o">.</span><span class="n">step</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
<span class="n">tokens</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="no">Generator</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">tokens</span><span class="o">[</span><span class="n">i</span><span class="o">].</span><span class="n">split</span><span class="p">(</span><span class="s2">""</span><span class="p">))</span>
<span class="k">end</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="n">incrementing</span> <span class="o">=</span> <span class="kp">false</span>
<span class="n">permutation</span> <span class="o">=</span> <span class="n">tokens</span><span class="o">.</span><span class="n">inject</span><span class="p">(</span><span class="nb">String</span><span class="o">.</span><span class="n">new</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">result</span><span class="p">,</span> <span class="n">token</span><span class="o">|</span>
<span class="k">if</span> <span class="n">token</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="nb">String</span><span class="p">)</span>
<span class="n">result</span> <span class="o">+</span> <span class="n">token</span>
<span class="k">else</span>
<span class="k">if</span> <span class="n">incrementing</span>
<span class="n">result</span> <span class="o">+</span> <span class="n">token</span><span class="o">.</span><span class="n">current</span>
<span class="k">else</span>
<span class="n">next_result</span> <span class="o">=</span> <span class="n">result</span> <span class="o">+</span> <span class="n">token</span><span class="o">.</span><span class="n">next</span>
<span class="k">if</span> <span class="n">token</span><span class="o">.</span><span class="n">end?</span>
<span class="n">token</span><span class="o">.</span><span class="n">rewind</span>
<span class="k">else</span>
<span class="n">incrementing</span> <span class="o">=</span> <span class="kp">true</span>
<span class="k">end</span>
<span class="n">next_result</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">yield</span><span class="p">(</span><span class="n">permutation</span><span class="p">)</span>
<span class="k">break</span> <span class="k">unless</span> <span class="n">incrementing</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">unless</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">1</span>
<span class="nb">abort</span> <span class="s2">"Usage: </span><span class="si">#{</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vg">$PROGRAM_NAME</span><span class="p">)</span><span class="si">}</span><span class="s2"> GENE_PATTERN"</span>
<span class="k">end</span>
<span class="n">permute_genes</span><span class="p">(</span><span class="no">ARGV</span><span class="o">.</span><span class="n">first</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">perm</span><span class="o">|</span> <span class="nb">puts</span> <span class="n">perm</span> <span class="p">}</span>
</pre></div>
<p>I think that solution comes out very nice. However, I must warn you that <code>Generator</code> is pretty slow due to some implementation details. If you run into performance issues while using it, just remember the backup plan (<code>to_a()</code>).</p>
<h4>A Web Robot</h4>
<p>I took MJD's "extended example" and Rubyified it, to the best of my abilities. It requires my <a href="http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/177589">RobotRules</a> port as well as the <a href="http://www.crummy.com/software/RubyfulSoup/">Rubyful Soup</a> library by Leonard Richardson.</p>
<p><em>[<strong>Note</strong>: Rubyful Soup is no longer maintained and probably doesn't work on modern versions of Ruby.]</em></p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby</span>
<span class="nb">require</span> <span class="s2">"open-uri"</span>
<span class="nb">require</span> <span class="s2">"uri"</span>
<span class="nb">require</span> <span class="s2">"robot_rules"</span>
<span class="nb">require</span> <span class="s2">"rubygems"</span>
<span class="nb">require</span> <span class="s2">"rubyful_soup"</span>
<span class="k">class</span> <span class="nc">SimpleRobot</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="o">&</span><span class="n">link_filter</span><span class="p">)</span>
<span class="n">filter</span><span class="p">(</span><span class="o">&</span><span class="n">link_filter</span><span class="p">)</span>
<span class="vi">@sites</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span>
<span class="vi">@rules</span> <span class="o">=</span> <span class="no">RobotRules</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">"SimpleRobot/1.0"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">filter</span><span class="p">(</span><span class="o">&</span><span class="n">link_filter</span><span class="p">)</span>
<span class="vi">@filter</span> <span class="o">=</span> <span class="n">link_filter</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">traverse</span><span class="p">(</span><span class="n">top</span><span class="p">)</span>
<span class="n">filter</span> <span class="o">=</span> <span class="vi">@filter</span> <span class="o">||</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">link</span><span class="o">|</span> <span class="n">link</span> <span class="o">=~</span> <span class="sr">/\A</span><span class="si">#{</span><span class="no">Regexp</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">top</span><span class="p">)</span><span class="si">}</span><span class="sr">/</span> <span class="p">}</span>
<span class="n">links</span> <span class="o">=</span> <span class="o">[[</span><span class="n">top</span><span class="p">,</span> <span class="s2">"Top link."</span><span class="o">]]</span>
<span class="n">seen</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="k">until</span> <span class="n">links</span><span class="o">.</span><span class="n">empty?</span>
<span class="n">url</span><span class="p">,</span> <span class="n">referrer</span> <span class="o">=</span> <span class="n">links</span><span class="o">.</span><span class="n">pop</span>
<span class="k">next</span> <span class="k">unless</span> <span class="n">robot_safe?</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="nb">open</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">page</span><span class="o">|</span>
<span class="n">content</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="k">if</span> <span class="n">page</span><span class="o">.</span><span class="n">content_type</span> <span class="o">=~</span> <span class="sr">/\Atext\/html\b/i</span>
<span class="n">tags</span> <span class="o">=</span> <span class="no">BeautifulSoup</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">content</span> <span class="o">=</span> <span class="n">page</span><span class="o">.</span><span class="n">read</span><span class="p">)</span>
<span class="n">links</span><span class="o">.</span><span class="n">push</span><span class="p">(</span> <span class="o">*</span><span class="n">tags</span><span class="o">.</span><span class="n">find_all</span><span class="p">(</span><span class="s2">"a"</span><span class="p">)</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="o">|</span> <span class="n">l</span><span class="o">.</span><span class="n">attrs</span><span class="o">[</span><span class="s2">"href"</span><span class="o">]</span> <span class="p">}</span><span class="o">.</span>
<span class="n">compact</span><span class="o">.</span>
<span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="o">|</span> <span class="n">l</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/#.*\Z/</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="p">}</span><span class="o">.</span>
<span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="o">|</span> <span class="n">l</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/\A(?!\w+:)/</span><span class="p">,</span> <span class="n">top</span><span class="p">)</span> <span class="p">}</span><span class="o">.</span>
<span class="nb">select</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="o">|</span> <span class="p">(</span><span class="n">seen</span><span class="o">[</span><span class="n">l</span><span class="o">]</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">}</span><span class="o">.</span>
<span class="nb">select</span><span class="p">(</span><span class="o">&</span><span class="n">filter</span><span class="p">)</span><span class="o">.</span>
<span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">l</span><span class="o">|</span> <span class="o">[</span><span class="n">l</span><span class="p">,</span> <span class="n">url</span><span class="o">]</span> <span class="p">}</span> <span class="p">)</span>
<span class="k">end</span>
<span class="k">yield</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">page</span><span class="o">.</span><span class="n">meta</span><span class="p">,</span> <span class="n">referrer</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
<span class="k">end</span> <span class="k">rescue</span> <span class="k">yield</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span><span class="p">,</span> <span class="n">referrer</span><span class="p">,</span> <span class="kp">nil</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">robot_safe?</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="n">location</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">uri</span><span class="o">.</span><span class="n">host</span><span class="si">}</span><span class="s2">:</span><span class="si">#{</span><span class="n">uri</span><span class="o">.</span><span class="n">port</span><span class="si">}</span><span class="s2">"</span>
<span class="k">return</span> <span class="kp">true</span> <span class="k">unless</span> <span class="sx">%w{http https}</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="n">uri</span><span class="o">.</span><span class="n">scheme</span><span class="p">)</span>
<span class="k">unless</span> <span class="vi">@sites</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="n">location</span><span class="p">)</span>
<span class="vi">@sites</span><span class="o">[</span><span class="n">location</span><span class="o">]</span> <span class="o">=</span> <span class="kp">true</span>
<span class="n">robot_url</span> <span class="o">=</span> <span class="s2">"http://</span><span class="si">#{</span><span class="n">location</span><span class="si">}</span><span class="s2">/robots.txt"</span>
<span class="k">begin</span>
<span class="n">robot_file</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">robot_url</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">page</span><span class="o">|</span> <span class="n">page</span><span class="o">.</span><span class="n">read</span> <span class="p">}</span>
<span class="k">rescue</span>
<span class="k">return</span> <span class="kp">true</span>
<span class="k">end</span>
<span class="vi">@rules</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">robot_url</span><span class="p">,</span> <span class="n">robot_file</span><span class="p">)</span>
<span class="k">end</span>
<span class="vi">@rules</span><span class="o">.</span><span class="n">allowed?</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>You can use that to quickly make crawlers:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby</span>
<span class="nb">require</span> <span class="s2">"simple_robot"</span>
<span class="n">bot</span> <span class="o">=</span> <span class="no">SimpleRobot</span><span class="o">.</span><span class="n">new</span>
<span class="n">bot</span><span class="o">.</span><span class="n">traverse</span><span class="p">(</span><span class="no">ARGV</span><span class="o">.</span><span class="n">shift</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">url</span><span class="p">,</span> <span class="n">head</span><span class="p">,</span> <span class="n">referrer</span><span class="p">,</span> <span class="n">html</span><span class="o">|</span>
<span class="nb">puts</span> <span class="n">url</span>
<span class="k">end</span>
</pre></div>James Edward Gray IICaching and Memoizationtag:graysoftinc.com,2006-01-20:/posts/72014-03-28T21:00:02ZIn this third article of the Higher-Order Ruby series, I develop a memoization library while exploring the concepts of caching.<p>I felt this chapter had a lot going for it, in places, but occasionally got lost in the details. All in all though, it's good stuff.</p>
<h4>Caching</h4>
<p>Obviously a powerful technique here and all of it translates to Ruby with little effort. Here's a direct translation of the <code>RGB_to_CMYK()</code> subroutine:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="vg">$cache</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span>
<span class="k">def</span> <span class="nf">rgb_to_cmyk</span><span class="p">(</span><span class="o">*</span><span class="n">rgb</span><span class="p">)</span>
<span class="k">return</span> <span class="vg">$cache</span><span class="o">[</span><span class="n">rgb</span><span class="o">]</span> <span class="k">if</span> <span class="vg">$cache</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="n">rgb</span><span class="p">)</span>
<span class="n">c</span><span class="p">,</span> <span class="n">m</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">rgb</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">color</span><span class="o">|</span> <span class="mi">255</span> <span class="o">-</span> <span class="n">color</span> <span class="p">}</span>
<span class="n">k</span> <span class="o">=</span> <span class="o">[</span><span class="n">c</span><span class="p">,</span> <span class="n">m</span><span class="p">,</span> <span class="n">y</span><span class="o">].</span><span class="n">min</span>
<span class="vg">$cache</span><span class="o">[</span><span class="n">rgb</span><span class="o">]</span> <span class="o">=</span> <span class="o">[</span><span class="n">c</span><span class="p">,</span> <span class="n">m</span><span class="p">,</span> <span class="n">y</span><span class="o">].</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">color</span><span class="o">|</span> <span class="n">color</span> <span class="o">-</span> <span class="n">k</span> <span class="p">}</span> <span class="o">+</span> <span class="o">[</span><span class="n">k</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">unless</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">3</span> <span class="o">&&</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">all?</span> <span class="p">{</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span> <span class="n">n</span> <span class="o">=~</span> <span class="sr">/\A\d+\Z/</span> <span class="p">}</span>
<span class="nb">abort</span> <span class="s2">"Usage: </span><span class="si">#{</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vg">$PROGRAM_NAME</span><span class="p">)</span><span class="si">}</span><span class="s2"> RED GREEN BLUE"</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">rgb_to_cmyk</span><span class="p">(</span><span class="o">*</span><span class="no">ARGV</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">num</span><span class="o">|</span> <span class="n">num</span><span class="o">.</span><span class="n">to_i</span> <span class="p">})</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">", "</span><span class="p">)</span>
</pre></div>
<p>There are several interesting syntax differences in there. For example, I had to use a global variable for the <code>$cache</code> because Ruby methods don't have access to local variables. Another option would be to use a <code>lambda()</code>. These are probably good indicators that we would wrap this in an object and use instance variables normally.</p>
<p>The other big difference is that a Ruby <code>Array</code> can be used as the key of a Ruby <code>Hash</code>. This fact alone means you can ignore most of the finding suitable keys discussion in this chapter.</p>
<p>Finally, I think Ruby's iterators just make the solution cleaner.</p>
<p>It's interesting to note that we could do this completely with a <code>Hash</code> in Ruby:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="n">cmyk</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">colors</span><span class="p">,</span> <span class="n">rgb</span><span class="o">|</span>
<span class="n">c</span><span class="p">,</span> <span class="n">m</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">rgb</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">color</span><span class="o">|</span> <span class="mi">255</span> <span class="o">-</span> <span class="n">color</span> <span class="p">}</span>
<span class="n">k</span> <span class="o">=</span> <span class="o">[</span><span class="n">c</span><span class="p">,</span> <span class="n">m</span><span class="p">,</span> <span class="n">y</span><span class="o">].</span><span class="n">min</span>
<span class="n">colors</span><span class="o">[</span><span class="n">rgb</span><span class="o">]</span> <span class="o">=</span> <span class="o">[</span><span class="n">c</span><span class="p">,</span> <span class="n">m</span><span class="p">,</span> <span class="n">y</span><span class="o">].</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">color</span><span class="o">|</span> <span class="n">color</span> <span class="o">-</span> <span class="n">k</span> <span class="p">}</span> <span class="o">+</span> <span class="o">[</span><span class="n">k</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">unless</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">3</span> <span class="o">&&</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">all?</span> <span class="p">{</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span> <span class="n">n</span> <span class="o">=~</span> <span class="sr">/\A\d+\Z/</span> <span class="p">}</span>
<span class="nb">abort</span> <span class="s2">"Usage: </span><span class="si">#{</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vg">$PROGRAM_NAME</span><span class="p">)</span><span class="si">}</span><span class="s2"> RED GREEN BLUE"</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="n">cmyk</span><span class="o">[</span><span class="no">ARGV</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">num</span><span class="o">|</span> <span class="n">num</span><span class="o">.</span><span class="n">to_i</span> <span class="p">}</span><span class="o">].</span><span class="n">join</span><span class="p">(</span><span class="s2">", "</span><span class="p">)</span>
</pre></div>
<p>I think using the default block of a <code>Hash</code> for caching like this has a lot of potential. I even suspect the performance is good, since Ruby's native <code>Hash</code> handles the dispatching. If the interface bothers you, passing an <code>Array</code> instead of RGB values, you can always wrap it in a method.</p>
<h4>Scope, Duration, and Lexical Closure</h4>
<p>This is simply a superb treatment of all of these topics. If you still struggle with understanding Ruby's closures or binding() or would just like a deeper understanding of variable scoping, this is a great place to pick up these details.</p>
<p>Just stay aware of Ruby's differences. <code>lambda()</code> is Ruby's anonymous subroutine, though not perfectly equivalent. Ruby's blocks are also closures and that's important to remember.</p>
<h4>Memoization</h4>
<p>Most of the details in here are helpful and interesting.</p>
<p>Along the way, it gets bogged down in minor Perlisms to eek out tiny improvements. I get sleepy when I start reading about that in any language.</p>
<p>There are still a lot of good tricks in here though and applicable to Ruby too. (The standard Ruby memoization process in Ruby is <code>alias</code>, then redefine, though you can get a reference with <code>method()</code> if you prefer.) Ruby does have a <a href="https://github.com/dkubb/memoizable">memoization library</a> as well.</p>
<p>Here's my own attempt to build a memoization library, which was trickier to get right than I expected:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="c1">#</span>
<span class="c1"># memoizable.rb</span>
<span class="c1">#</span>
<span class="c1"># Created by James Edward Gray II on 2006-01-21.</span>
<span class="c1"># Copyright 2006 Gray Productions. All rights reserved.</span>
<span class="c1">#</span>
<span class="c1"># </span>
<span class="c1"># Have your class or module <tt>extend Memoizable</tt> to gain access to the </span>
<span class="c1"># #memoize method.</span>
<span class="c1"># </span>
<span class="k">module</span> <span class="nn">Memoizable</span>
<span class="c1"># </span>
<span class="c1"># This method is used to replace a computationally expensive method with an</span>
<span class="c1"># equivalent method that will answer repeat calls for indentical arguments </span>
<span class="c1"># from a _cache_. To use, make sure the current class extends Memoizable, </span>
<span class="c1"># then call by passing the _name_ of the method you wish to cache results for.</span>
<span class="c1"># </span>
<span class="c1"># The _cache_ object can be any object supporting both #[] and #[]=. The keys</span>
<span class="c1"># used for the _cache_ are an Array of the arguments the method was called </span>
<span class="c1"># with and the values are just the returned results of the original method </span>
<span class="c1"># call. The default _cache_ is a simple Hash, providing in-memory storage.</span>
<span class="c1"># </span>
<span class="k">def</span> <span class="nf">memoize</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">cache</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span><span class="p">)</span>
<span class="n">original</span> <span class="o">=</span> <span class="s2">"__unmemoized_</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">__"</span>
<span class="c1"># </span>
<span class="c1"># <tt>self.class</tt> is used for the top level, to modify Object, otherwise</span>
<span class="c1"># we just modify the Class or Module directly</span>
<span class="c1"># </span>
<span class="p">(</span><span class="o">[</span><span class="no">Class</span><span class="p">,</span> <span class="no">Module</span><span class="o">].</span><span class="n">include?</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="p">)</span> <span class="p">?</span> <span class="nb">self</span> <span class="p">:</span> <span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="p">)</span><span class="o">.</span><span class="n">class_eval</span> <span class="k">do</span>
<span class="n">alias_method</span> <span class="n">original</span><span class="p">,</span> <span class="nb">name</span>
<span class="kp">private</span> <span class="n">original</span>
<span class="n">define_method</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span> <span class="p">{</span> <span class="o">|*</span><span class="n">args</span><span class="o">|</span> <span class="n">cache</span><span class="o">[</span><span class="n">args</span><span class="o">]</span> <span class="o">||=</span> <span class="nb">send</span><span class="p">(</span><span class="n">original</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Standard intended usage looks like this:</p>
<div class="highlight highlight-ruby"><pre><span class="k">class</span> <span class="nc">Fibonacci</span>
<span class="kp">extend</span> <span class="no">Memoizable</span>
<span class="k">def</span> <span class="nf">fib</span><span class="p">(</span><span class="n">num</span><span class="p">)</span>
<span class="k">return</span> <span class="n">num</span> <span class="k">if</span> <span class="n">num</span> <span class="o"><</span> <span class="mi">2</span>
<span class="n">fib</span><span class="p">(</span><span class="n">num</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">fib</span><span class="p">(</span><span class="n">num</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">memoize</span> <span class="ss">:fib</span>
<span class="k">end</span>
</pre></div>
<p>Or, for a class/module method:</p>
<div class="highlight highlight-ruby"><pre><span class="k">class</span> <span class="nc">Fibonacci</span>
<span class="k">class</span> <span class="o"><<</span> <span class="nb">self</span>
<span class="kp">extend</span> <span class="no">Memoizable</span>
<span class="k">def</span> <span class="nf">fib</span><span class="p">(</span><span class="n">num</span><span class="p">)</span>
<span class="k">return</span> <span class="n">num</span> <span class="k">if</span> <span class="n">num</span> <span class="o"><</span> <span class="mi">2</span>
<span class="n">fib</span><span class="p">(</span><span class="n">num</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">fib</span><span class="p">(</span><span class="n">num</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">memoize</span> <span class="ss">:fib</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>It even works for top-level methods:</p>
<div class="highlight highlight-ruby"><pre><span class="kp">extend</span> <span class="no">Memoizable</span>
<span class="k">def</span> <span class="nf">fib</span><span class="p">(</span><span class="n">num</span><span class="p">)</span>
<span class="k">return</span> <span class="n">num</span> <span class="k">if</span> <span class="n">num</span> <span class="o"><</span> <span class="mi">2</span>
<span class="n">fib</span><span class="p">(</span><span class="n">num</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">fib</span><span class="p">(</span><span class="n">num</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">memoize</span> <span class="ss">:fib</span>
</pre></div>
<p>Finally, my library allows you to supply a custom cache. Here's an example using weak references:</p>
<p><em>[<strong>Note</strong>: The following code uses <code>Thread.critical = true</code> which is no longer supported in Ruby.]</em></p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="c1">#</span>
<span class="c1"># weak_references.rb</span>
<span class="c1">#</span>
<span class="c1"># Created by James Edward Gray II on 2006-01-23.</span>
<span class="c1"># Copyright 2006 Gray Productions. All rights reserved.</span>
<span class="c1">#</span>
<span class="nb">require</span> <span class="s2">"memoizable"</span>
<span class="c1"># </span>
<span class="c1"># This cache uses weak references, which can be garbage collected. When the </span>
<span class="c1"># cache is checked, the value will be returned if it is still around, otherwise</span>
<span class="c1"># +nil+ is returned.</span>
<span class="c1"># </span>
<span class="c1"># (Code by Mauricio Fernandez.)</span>
<span class="c1"># </span>
<span class="k">class</span> <span class="nc">WeakCache</span>
<span class="k">def</span> <span class="nf">initialize</span>
<span class="n">set_internal_hash</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">method_missing</span><span class="p">(</span><span class="n">meth</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="n">__get_hash__</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">meth</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">__get_hash__</span>
<span class="n">old_critical</span> <span class="o">=</span> <span class="no">Thread</span><span class="o">.</span><span class="n">critical</span>
<span class="no">Thread</span><span class="o">.</span><span class="n">critical</span> <span class="o">=</span> <span class="kp">true</span>
<span class="vi">@valid</span> <span class="ow">or</span> <span class="n">set_internal_hash</span>
<span class="k">return</span> <span class="no">ObjectSpace</span><span class="o">.</span><span class="n">_id2ref</span><span class="p">(</span><span class="vi">@hash_id</span><span class="p">)</span>
<span class="k">ensure</span>
<span class="no">Thread</span><span class="o">.</span><span class="n">critical</span> <span class="o">=</span> <span class="n">old_critical</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">set_internal_hash</span>
<span class="nb">hash</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span>
<span class="vi">@hash_id</span> <span class="o">=</span> <span class="nb">hash</span><span class="o">.</span><span class="n">object_id</span>
<span class="vi">@valid</span> <span class="o">=</span> <span class="kp">true</span>
<span class="no">ObjectSpace</span><span class="o">.</span><span class="n">define_finalizer</span><span class="p">(</span><span class="nb">hash</span><span class="p">,</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="vi">@valid</span> <span class="o">=</span> <span class="kp">false</span> <span class="p">})</span>
<span class="nb">hash</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="vg">$cache</span> <span class="o">=</span> <span class="no">WeakCache</span><span class="o">.</span><span class="n">new</span> <span class="c1"># makes the example below easier to show</span>
<span class="k">class</span> <span class="nc">Fibonacci</span>
<span class="kp">extend</span> <span class="no">Memoizable</span>
<span class="k">def</span> <span class="nf">fib</span><span class="p">(</span><span class="n">num</span><span class="p">)</span>
<span class="k">return</span> <span class="n">num</span> <span class="k">if</span> <span class="n">num</span> <span class="o"><</span> <span class="mi">2</span>
<span class="n">fib</span><span class="p">(</span><span class="n">num</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">fib</span><span class="p">(</span><span class="n">num</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">memoize</span> <span class="ss">:fib</span><span class="p">,</span> <span class="vg">$cache</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="s2">"This method is memoized using a weak reference cache..."</span>
<span class="n">start</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
<span class="nb">puts</span> <span class="s2">"fib(100): </span><span class="si">#{</span><span class="no">Fibonacci</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">fib</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span>
<span class="nb">puts</span> <span class="s2">"Run time: </span><span class="si">#{</span><span class="no">Time</span><span class="o">.</span><span class="n">now</span> <span class="o">-</span> <span class="n">start</span><span class="si">}</span><span class="s2"> seconds"</span>
<span class="nb">puts</span>
<span class="nb">puts</span> <span class="s2">"We will now try to get garbage collection to reclaim the cache..."</span>
<span class="nb">puts</span> <span class="s2">"Cache size: </span><span class="si">#{</span><span class="vg">$cache</span><span class="o">.</span><span class="n">size</span><span class="si">}</span><span class="s2">"</span>
<span class="nb">puts</span> <span class="s2">"Running garbage collection..."</span>
<span class="no">ObjectSpace</span><span class="o">.</span><span class="n">garbage_collect</span>
<span class="nb">puts</span> <span class="s2">"Cache size: </span><span class="si">#{</span><span class="vg">$cache</span><span class="o">.</span><span class="n">size</span><span class="si">}</span><span class="s2">"</span>
<span class="nb">puts</span>
<span class="nb">puts</span> <span class="s2">"Running the test again may not be instant, but still fast..."</span>
<span class="n">start</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
<span class="nb">puts</span> <span class="s2">"fib(100): </span><span class="si">#{</span><span class="no">Fibonacci</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">fib</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span>
<span class="nb">puts</span> <span class="s2">"Run time: </span><span class="si">#{</span><span class="no">Time</span><span class="o">.</span><span class="n">now</span> <span class="o">-</span> <span class="n">start</span><span class="si">}</span><span class="s2"> seconds"</span>
</pre></div>
<h4>The Missing Trade Off</h4>
<p>I was kind-of surprised that this chapter talked a lot about speed yet barely mentioned memory consumption. Memoization is a trade-off of space (RAM or disk) for speed. Sometimes the space aspect makes it prohibitively expensive, though there is virtually no mention of that here. I wonder if that has anything to do with speed being much easier to measure than memory consumption, in languages like Perl and Ruby…</p>James Edward Gray IIDispatch Tablestag:graysoftinc.com,2006-01-17:/posts/62014-03-28T20:55:59ZIn this second article of the High-Order Ruby series, I take a look at method_missing() as a substitute for dispatch tables.<p>I think in Ruby we tend to do a lot of this kind of work with <code>method_missing()</code>. I told you, Functional OO Programming.</p>
<p>Here's my attempt at something close to a direct translation of the RPN calculator example:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="vg">$stack</span> <span class="o">=</span> <span class="nb">Array</span><span class="o">.</span><span class="n">new</span>
<span class="k">def</span> <span class="nf">rpn</span><span class="p">(</span><span class="n">expression</span><span class="p">,</span> <span class="n">operations_table</span><span class="p">)</span>
<span class="n">tokens</span> <span class="o">=</span> <span class="n">expression</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span>
<span class="n">tokens</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">token</span><span class="o">|</span>
<span class="n">type</span> <span class="o">=</span> <span class="n">token</span> <span class="o">=~</span> <span class="sr">/\A\d+\Z/</span> <span class="p">?</span> <span class="ss">:number</span> <span class="p">:</span> <span class="kp">nil</span>
<span class="n">operations_table</span><span class="o">[</span><span class="n">type</span> <span class="o">||</span> <span class="n">token</span><span class="o">][</span><span class="n">token</span><span class="o">]</span>
<span class="k">end</span>
<span class="vg">$stack</span><span class="o">.</span><span class="n">pop</span>
<span class="k">end</span>
<span class="k">if</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">2</span> <span class="o">&&</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">first</span> <span class="o">==</span> <span class="s2">"-i"</span> <span class="o">&&</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">last</span> <span class="o">=~</span> <span class="sr">/\A[-+*\/0-9 ]+\Z/</span>
<span class="nb">require</span> <span class="s2">"pp"</span>
<span class="k">def</span> <span class="nf">ast_to_infix</span><span class="p">(</span><span class="n">ast</span><span class="p">)</span>
<span class="k">if</span> <span class="n">ast</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="nb">Array</span><span class="p">)</span>
<span class="n">op</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span> <span class="o">=</span> <span class="n">ast</span>
<span class="s2">"(</span><span class="si">#{</span><span class="n">ast_to_infix</span><span class="p">(</span><span class="n">left</span><span class="p">)</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">op</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">ast_to_infix</span><span class="p">(</span><span class="n">right</span><span class="p">)</span><span class="si">}</span><span class="s2">)"</span>
<span class="k">else</span>
<span class="n">ast</span><span class="o">.</span><span class="n">to_s</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">ast_table</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">table</span><span class="p">,</span> <span class="n">token</span><span class="o">|</span>
<span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">op</span><span class="o">|</span> <span class="n">s</span> <span class="o">=</span> <span class="vg">$stack</span><span class="o">.</span><span class="n">pop</span><span class="p">;</span> <span class="vg">$stack</span> <span class="o"><<</span> <span class="o">[</span><span class="n">op</span><span class="p">,</span> <span class="vg">$stack</span><span class="o">.</span><span class="n">pop</span><span class="p">,</span> <span class="n">s</span><span class="o">]</span> <span class="p">}</span>
<span class="k">end</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span><span class="ss">:number</span> <span class="o">=></span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">num</span><span class="o">|</span> <span class="vg">$stack</span> <span class="o"><<</span> <span class="n">num</span><span class="o">.</span><span class="n">to_i</span> <span class="p">})</span>
<span class="nb">puts</span> <span class="s2">"AST:"</span>
<span class="n">pp</span><span class="p">(</span><span class="n">ast</span> <span class="o">=</span> <span class="n">rpn</span><span class="p">(</span><span class="no">ARGV</span><span class="o">.</span><span class="n">last</span><span class="p">,</span> <span class="n">ast_table</span><span class="p">))</span>
<span class="nb">puts</span> <span class="s2">"Infix:"</span>
<span class="n">pp</span> <span class="n">ast_to_infix</span><span class="p">(</span><span class="n">ast</span><span class="p">)</span>
<span class="k">elsif</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">1</span> <span class="o">&&</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">first</span> <span class="o">=~</span> <span class="sr">/\A[-+*\/0-9 ]+\Z/</span>
<span class="n">calculation_table</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">table</span><span class="p">,</span> <span class="n">token</span><span class="o">|</span>
<span class="k">raise</span> <span class="s2">"Unknown token: </span><span class="si">#{</span><span class="n">token</span><span class="si">}</span><span class="s2">."</span>
<span class="k">end</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span>
<span class="ss">:number</span> <span class="o">=></span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">num</span><span class="o">|</span> <span class="vg">$stack</span> <span class="o"><<</span> <span class="n">num</span><span class="o">.</span><span class="n">to_i</span> <span class="p">},</span>
<span class="s2">"+"</span> <span class="o">=></span> <span class="nb">lambda</span> <span class="p">{</span> <span class="vg">$stack</span> <span class="o"><<</span> <span class="vg">$stack</span><span class="o">.</span><span class="n">pop</span> <span class="o">+</span> <span class="vg">$stack</span><span class="o">.</span><span class="n">pop</span> <span class="p">},</span>
<span class="s2">"-"</span> <span class="o">=></span> <span class="nb">lambda</span> <span class="p">{</span> <span class="n">s</span> <span class="o">=</span> <span class="vg">$stack</span><span class="o">.</span><span class="n">pop</span><span class="p">;</span> <span class="vg">$stack</span> <span class="o"><<</span> <span class="vg">$stack</span><span class="o">.</span><span class="n">pop</span> <span class="o">-</span> <span class="n">s</span> <span class="p">},</span>
<span class="s2">"*"</span> <span class="o">=></span> <span class="nb">lambda</span> <span class="p">{</span> <span class="vg">$stack</span> <span class="o"><<</span> <span class="vg">$stack</span><span class="o">.</span><span class="n">pop</span> <span class="o">*</span> <span class="vg">$stack</span><span class="o">.</span><span class="n">pop</span> <span class="p">},</span>
<span class="s2">"/"</span> <span class="o">=></span> <span class="nb">lambda</span> <span class="p">{</span> <span class="n">d</span> <span class="o">=</span> <span class="vg">$stack</span><span class="o">.</span><span class="n">pop</span><span class="p">;</span> <span class="vg">$stack</span> <span class="o"><<</span> <span class="vg">$stack</span><span class="o">.</span><span class="n">pop</span> <span class="o">/</span> <span class="n">d</span> <span class="p">}</span>
<span class="p">)</span>
<span class="nb">puts</span> <span class="n">rpn</span><span class="p">(</span><span class="no">ARGV</span><span class="o">.</span><span class="n">first</span><span class="p">,</span> <span class="n">calculation_table</span><span class="p">)</span>
<span class="k">else</span>
<span class="nb">puts</span> <span class="s2">"Usage: </span><span class="si">#{</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vg">$PROGRAM_NAME</span><span class="p">)</span><span class="si">}</span><span class="s2"> [-i] RPN_EXPRESSION"</span>
<span class="k">end</span>
</pre></div>
<p>I originally thought I was being clever using the default block of a <code>Hash</code>, but the <code>lambda()</code> trick in the AST translator feels bumpy. I also don't like the global <code>$stack</code>. And types are under used. Let me try to fix those issues and convert the API to something a bit more in Ruby's style:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="k">class</span> <span class="nc">RPN</span>
<span class="k">def</span> <span class="nf">initialize</span>
<span class="vi">@operations_table</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">.</span><span class="n">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">table</span><span class="p">,</span> <span class="n">token</span><span class="o">|</span>
<span class="k">if</span> <span class="n">table</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="ss">:default</span><span class="p">)</span>
<span class="n">table</span><span class="o">[</span><span class="ss">:default</span><span class="o">]</span>
<span class="k">else</span>
<span class="k">raise</span> <span class="s2">"Unknow token: </span><span class="si">#{</span><span class="n">token</span><span class="si">}</span><span class="s2">."</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="vi">@types</span> <span class="o">=</span> <span class="o">[[</span><span class="ss">:number</span><span class="p">,</span> <span class="sr">/\A\d+\Z/</span><span class="o">]]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">type</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">pattern</span><span class="p">)</span>
<span class="vi">@types</span> <span class="o"><<</span> <span class="o">[</span><span class="nb">name</span><span class="p">,</span> <span class="n">pattern</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">method_missing</span><span class="p">(</span><span class="n">meth</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="vi">@operations_table</span><span class="o">[</span><span class="n">meth</span><span class="o">.</span><span class="n">to_sym</span><span class="o">]</span> <span class="o">=</span> <span class="n">block</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="n">expression</span><span class="p">,</span> <span class="n">stack</span> <span class="o">=</span> <span class="nb">Array</span><span class="o">.</span><span class="n">new</span><span class="p">)</span>
<span class="n">expression</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">token</span><span class="o">|</span>
<span class="k">case</span> <span class="p">(</span><span class="n">type</span> <span class="o">=</span> <span class="n">find_type</span><span class="p">(</span><span class="n">token</span><span class="p">))</span>
<span class="k">when</span> <span class="ss">:binary_op</span>
<span class="n">call_binary_op</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="n">stack</span><span class="p">,</span> <span class="o">&</span><span class="vi">@operations_table</span><span class="o">[</span><span class="ss">:binary_op</span><span class="o">]</span><span class="p">)</span>
<span class="k">else</span>
<span class="vi">@operations_table</span><span class="o">[</span><span class="n">type</span> <span class="o">||</span> <span class="n">token</span><span class="o">][</span><span class="n">token</span><span class="p">,</span> <span class="n">stack</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">stack</span><span class="o">.</span><span class="n">pop</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">find_type</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
<span class="p">(</span><span class="n">type</span> <span class="o">=</span> <span class="vi">@types</span><span class="o">.</span><span class="n">find</span> <span class="p">{</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span> <span class="n">t</span><span class="o">.</span><span class="n">last</span> <span class="o">===</span> <span class="n">token</span> <span class="p">})</span> <span class="p">?</span> <span class="n">type</span><span class="o">.</span><span class="n">first</span> <span class="p">:</span> <span class="kp">nil</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">call_binary_op</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">stack</span><span class="p">,</span> <span class="o">&</span><span class="n">operation</span><span class="p">)</span>
<span class="n">right</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">pop</span>
<span class="n">operation</span><span class="o">[</span><span class="n">operator</span><span class="p">,</span> <span class="n">stack</span><span class="o">.</span><span class="n">pop</span><span class="p">,</span> <span class="n">right</span><span class="p">,</span> <span class="n">stack</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">if</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">2</span> <span class="o">&&</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">first</span> <span class="o">==</span> <span class="s2">"-i"</span> <span class="o">&&</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">last</span> <span class="o">=~</span> <span class="sr">/\A[-+*\/0-9 ]+\Z/</span>
<span class="nb">require</span> <span class="s2">"pp"</span>
<span class="k">def</span> <span class="nf">ast_to_infix</span><span class="p">(</span><span class="n">ast</span><span class="p">)</span>
<span class="k">if</span> <span class="n">ast</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="nb">Array</span><span class="p">)</span>
<span class="n">op</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span> <span class="o">=</span> <span class="n">ast</span>
<span class="s2">"(</span><span class="si">#{</span><span class="n">ast_to_infix</span><span class="p">(</span><span class="n">left</span><span class="p">)</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">op</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">ast_to_infix</span><span class="p">(</span><span class="n">right</span><span class="p">)</span><span class="si">}</span><span class="s2">)"</span>
<span class="k">else</span>
<span class="n">ast</span><span class="o">.</span><span class="n">to_s</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">calc</span> <span class="o">=</span> <span class="no">RPN</span><span class="o">.</span><span class="n">new</span>
<span class="n">calc</span><span class="o">.</span><span class="n">type</span><span class="p">(</span><span class="ss">:binary_op</span><span class="p">,</span> <span class="sr">/\A[-+*\/]\Z/</span><span class="p">)</span>
<span class="n">calc</span><span class="o">.</span><span class="n">number</span> <span class="p">{</span> <span class="o">|</span><span class="n">num</span><span class="p">,</span> <span class="n">stack</span><span class="o">|</span> <span class="n">stack</span> <span class="o"><<</span> <span class="n">num</span><span class="o">.</span><span class="n">to_i</span> <span class="p">}</span>
<span class="n">calc</span><span class="o">.</span><span class="n">binary_op</span> <span class="p">{</span> <span class="o">|</span><span class="n">op</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">,</span> <span class="n">stack</span><span class="o">|</span> <span class="n">stack</span> <span class="o"><<</span> <span class="o">[</span><span class="n">op</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="o">]</span> <span class="p">}</span>
<span class="nb">puts</span> <span class="s2">"AST:"</span>
<span class="n">pp</span><span class="p">(</span><span class="n">ast</span> <span class="o">=</span> <span class="n">calc</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="no">ARGV</span><span class="o">.</span><span class="n">last</span><span class="p">))</span>
<span class="nb">puts</span> <span class="s2">"Infix:"</span>
<span class="n">pp</span> <span class="n">ast_to_infix</span><span class="p">(</span><span class="n">ast</span><span class="p">)</span>
<span class="k">elsif</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">1</span> <span class="o">&&</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">first</span> <span class="o">=~</span> <span class="sr">/\A[-+*\/0-9 ]+\Z/</span>
<span class="n">calc</span> <span class="o">=</span> <span class="no">RPN</span><span class="o">.</span><span class="n">new</span>
<span class="n">calc</span><span class="o">.</span><span class="n">type</span><span class="p">(</span><span class="ss">:binary_op</span><span class="p">,</span> <span class="sr">/\A[-+*\/]\Z/</span><span class="p">)</span>
<span class="n">calc</span><span class="o">.</span><span class="n">number</span> <span class="p">{</span> <span class="o">|</span><span class="n">num</span><span class="p">,</span> <span class="n">stack</span><span class="o">|</span> <span class="n">stack</span> <span class="o"><<</span> <span class="n">num</span><span class="o">.</span><span class="n">to_i</span> <span class="p">}</span>
<span class="n">calc</span><span class="o">.</span><span class="n">binary_op</span> <span class="p">{</span> <span class="o">|</span><span class="n">op</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">,</span> <span class="n">stack</span><span class="o">|</span> <span class="n">stack</span> <span class="o"><<</span> <span class="n">left</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="n">right</span><span class="p">)</span> <span class="p">}</span>
<span class="nb">puts</span> <span class="n">calc</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="no">ARGV</span><span class="o">.</span><span class="n">first</span><span class="p">)</span>
<span class="k">else</span>
<span class="nb">puts</span> <span class="s2">"Usage: </span><span class="si">#{</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vg">$PROGRAM_NAME</span><span class="p">)</span><span class="si">}</span><span class="s2"> [-i] RPN_EXPRESSION"</span>
<span class="k">end</span>
</pre></div>
<p>I'm not sure if I got everything perfect, but hopefully there's a good Ruby idiom or two hiding in there. It certainly <em>feels</em> more Rubyish. Of course, it is almost 30 lines longer…</p>James Edward Gray IIRecursion and Callbackstag:graysoftinc.com,2006-01-17:/posts/52014-03-28T20:51:34ZThis article is the beginning of a new series where I will share some of the great ideas from the Higher-Order Perl book by Mark Jason Dominus. Of course, I'll examine these concepts in Ruby. This first article takes a look at recursive methods and Ruby blocks as a callback functions.<p>I'm currently reading through <a href="http://hop.perl.plover.com/">Higher-Order Perl</a>, by Mark Jason Dominus. (Yes, I read books about things other than Ruby.)</p>
<p>So far, I'm enjoying the title quite a bit. It certainly has me thinking and the Perl in it is very clean and easy to understand. That helps me translate the concepts to my language of interest.</p>
<p>I'll post some of my Ruby translations of the books example code here as I go along. Others familiar with the book might enjoy looking over them. Be warned, my comments might not make much sense to those who haven't read the book.</p>
<h4>Recursion</h4>
<p>The book starts with some very simple recursion examples trivially translated. Here's one for manually translating <code>Integer</code>s to binary <code>String</code>s (a long way to say <code>str.to_i.to_s(2)</code> in Ruby):</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="k">def</span> <span class="nf">binary</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
<span class="k">return</span> <span class="n">number</span><span class="o">.</span><span class="n">to_s</span> <span class="k">if</span> <span class="o">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="o">].</span><span class="n">include?</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
<span class="n">k</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">number</span><span class="o">.</span><span class="n">divmod</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="n">binary</span><span class="p">(</span><span class="n">k</span><span class="p">)</span> <span class="o">+</span> <span class="n">b</span><span class="o">.</span><span class="n">to_s</span>
<span class="k">end</span>
<span class="k">unless</span> <span class="o">!</span><span class="no">ARGV</span><span class="o">.</span><span class="n">empty?</span> <span class="o">&&</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">all?</span> <span class="p">{</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span> <span class="n">n</span> <span class="o">=~</span> <span class="sr">/\A\d+\Z/</span> <span class="p">}</span>
<span class="nb">abort</span> <span class="s2">"Usage: </span><span class="si">#{</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vg">$PROGRAM_NAME</span><span class="p">)</span><span class="si">}</span><span class="s2"> DECIMAL_NUMBERS"</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">num</span><span class="o">|</span> <span class="n">binary</span><span class="p">(</span><span class="n">num</span><span class="o">.</span><span class="n">to_i</span><span class="p">)</span> <span class="p">}</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span>
</pre></div>
<p>Here's another for calculating factorials:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="k">def</span> <span class="nf">factorial</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
<span class="k">return</span> <span class="mi">1</span> <span class="k">if</span> <span class="n">number</span> <span class="o">==</span> <span class="mi">0</span>
<span class="n">factorial</span><span class="p">(</span><span class="n">number</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">number</span>
<span class="k">end</span>
<span class="k">unless</span> <span class="o">!</span><span class="no">ARGV</span><span class="o">.</span><span class="n">empty?</span> <span class="o">&&</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">all?</span> <span class="p">{</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span> <span class="n">n</span> <span class="o">=~</span> <span class="sr">/\A\d+\Z/</span> <span class="p">}</span>
<span class="nb">abort</span> <span class="s2">"Usage: </span><span class="si">#{</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vg">$PROGRAM_NAME</span><span class="p">)</span><span class="si">}</span><span class="s2"> DECIMAL_NUMBERS"</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">num</span><span class="o">|</span> <span class="n">factorial</span><span class="p">(</span><span class="n">num</span><span class="o">.</span><span class="n">to_i</span><span class="p">)</span> <span class="p">}</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span>
</pre></div>
<p>MJD does talk about how recursion can always be unrolled into an iterative solution. That leads to what is probably a more natural Ruby solution for examples like these. Here's how I would probably code <code>factorial()</code>:</p>
<div class="highlight highlight-ruby"><pre><span class="k">def</span> <span class="nf">factorial</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
<span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="n">.number</span><span class="p">)</span><span class="o">.</span><span class="n">inject</span> <span class="p">{</span> <span class="o">|</span><span class="n">prod</span><span class="p">,</span> <span class="n">n</span><span class="o">|</span> <span class="n">prod</span> <span class="o">*</span> <span class="n">n</span> <span class="p">}</span>
<span class="k">end</span>
</pre></div>
<p>Of course, that's just not what this chapter is about.</p>
<h4>Callbacks</h4>
<p>This section starts off by showing just how cool Ruby's blocks can be:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="k">def</span> <span class="nf">hanoi</span><span class="p">(</span><span class="n">disk</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="n">finish</span><span class="p">,</span> <span class="n">extra</span><span class="p">,</span> <span class="o">&</span><span class="n">mover</span><span class="p">)</span>
<span class="k">if</span> <span class="n">disk</span> <span class="o">==</span> <span class="mi">1</span>
<span class="n">mover</span><span class="o">[</span><span class="n">disk</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="n">finish</span><span class="o">]</span>
<span class="k">else</span>
<span class="n">hanoi</span><span class="p">(</span><span class="n">disk</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="n">extra</span><span class="p">,</span> <span class="n">finish</span><span class="p">,</span> <span class="o">&</span><span class="n">mover</span><span class="p">)</span>
<span class="n">mover</span><span class="o">[</span><span class="n">disk</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="n">finish</span><span class="o">]</span>
<span class="n">hanoi</span><span class="p">(</span><span class="n">disk</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">extra</span><span class="p">,</span> <span class="n">finish</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="o">&</span><span class="n">mover</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">disks</span> <span class="o">=</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">empty?</span> <span class="p">?</span> <span class="mi">64</span> <span class="p">:</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">first</span><span class="o">.</span><span class="n">to_i</span>
<span class="n">positions</span> <span class="o">=</span> <span class="o">[</span><span class="kp">nil</span><span class="o">]</span> <span class="o">+</span> <span class="o">[</span><span class="s2">"A"</span><span class="o">]</span> <span class="o">*</span> <span class="n">disks</span>
<span class="n">hanoi</span><span class="p">(</span><span class="n">disks</span><span class="p">,</span> <span class="s2">"A"</span><span class="p">,</span> <span class="s2">"C"</span><span class="p">,</span> <span class="s2">"B"</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">disk</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="n">finish</span><span class="o">|</span>
<span class="k">if</span> <span class="n">disk</span> <span class="o"><</span> <span class="mi">1</span> <span class="o">||</span> <span class="n">disk</span> <span class="o">></span> <span class="n">positions</span><span class="o">.</span><span class="n">size</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">raise</span> <span class="s2">"Bad disk number: </span><span class="si">#{</span><span class="n">disk</span><span class="si">}</span><span class="s2">. Disks should be between 1 and </span><span class="si">#{</span><span class="n">disks</span><span class="si">}</span><span class="s2">."</span>
<span class="k">end</span>
<span class="k">unless</span> <span class="n">positions</span><span class="o">[</span><span class="n">disk</span><span class="o">]</span> <span class="o">==</span> <span class="n">start</span>
<span class="k">raise</span> <span class="s2">"Tried to move #</span><span class="si">#{</span><span class="n">disk</span><span class="si">}</span><span class="s2"> from </span><span class="si">#{</span><span class="n">start</span><span class="si">}</span><span class="s2">, "</span> <span class="o">+</span>
<span class="s2">"but it is on peg </span><span class="si">#{</span><span class="n">positions</span><span class="o">[</span><span class="n">disk</span><span class="o">]</span><span class="si">}</span><span class="s2">."</span>
<span class="k">end</span>
<span class="p">(</span><span class="mi">1</span><span class="o">.</span><span class="n">.</span><span class="o">.</span><span class="n">disk</span><span class="p">)</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">smaller_disk</span><span class="o">|</span>
<span class="k">if</span> <span class="n">positions</span><span class="o">[</span><span class="n">smaller_disk</span><span class="o">]</span> <span class="o">==</span> <span class="n">start</span>
<span class="k">raise</span> <span class="s2">"Cannot move </span><span class="si">#{</span><span class="n">disk</span><span class="si">}</span><span class="s2"> from </span><span class="si">#{</span><span class="n">start</span><span class="si">}</span><span class="s2">, "</span> <span class="o">+</span>
<span class="s2">"because </span><span class="si">#{</span><span class="n">smaller_disk</span><span class="si">}</span><span class="s2"> is on top of it."</span>
<span class="k">elsif</span> <span class="n">positions</span><span class="o">[</span><span class="n">smaller_disk</span><span class="o">]</span> <span class="o">==</span> <span class="n">finish</span>
<span class="k">raise</span> <span class="s2">"Cannot move </span><span class="si">#{</span><span class="n">disk</span><span class="si">}</span><span class="s2"> to </span><span class="si">#{</span><span class="n">finish</span><span class="si">}</span><span class="s2">, "</span> <span class="o">+</span>
<span class="s2">"because </span><span class="si">#{</span><span class="n">smaller_disk</span><span class="si">}</span><span class="s2"> is already there."</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="s2">"Move #</span><span class="si">#{</span><span class="n">disk</span><span class="si">}</span><span class="s2"> from </span><span class="si">#{</span><span class="n">start</span><span class="si">}</span><span class="s2"> to </span><span class="si">#{</span><span class="n">finish</span><span class="si">}</span><span class="s2">."</span>
<span class="n">positions</span><span class="o">[</span><span class="n">disk</span><span class="o">]</span> <span class="o">=</span> <span class="n">finish</span>
<span class="k">end</span>
</pre></div>
<p>Interestingly though, MJD quickly gets into passing around multiple anonymous functions, and Ruby has to do that just like Perl:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/local/bin/ruby -w</span>
<span class="k">def</span> <span class="nf">dir_walk</span><span class="p">(</span><span class="n">top</span><span class="p">,</span> <span class="n">file_proc</span><span class="p">,</span> <span class="n">dir_proc</span><span class="p">)</span>
<span class="n">total</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">size</span><span class="p">(</span><span class="n">top</span><span class="p">)</span>
<span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">directory?</span><span class="p">(</span><span class="n">top</span><span class="p">)</span>
<span class="n">results</span> <span class="o">=</span> <span class="nb">Array</span><span class="o">.</span><span class="n">new</span>
<span class="no">Dir</span><span class="o">.</span><span class="n">foreach</span><span class="p">(</span><span class="n">top</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">file_name</span><span class="o">|</span>
<span class="k">next</span> <span class="k">if</span> <span class="sx">%w{. ..}</span><span class="o">.</span><span class="n">include?</span><span class="p">(</span><span class="n">file_name</span><span class="p">)</span>
<span class="n">results</span> <span class="o">+=</span> <span class="n">dir_walk</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">top</span><span class="p">,</span> <span class="n">file_name</span><span class="p">),</span> <span class="n">file_proc</span><span class="p">,</span> <span class="n">dir_proc</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">dir_proc</span><span class="o">.</span><span class="n">nil?</span> <span class="p">?</span> <span class="nb">Array</span><span class="o">.</span><span class="n">new</span> <span class="p">:</span> <span class="n">dir_proc</span><span class="o">[</span><span class="n">top</span><span class="p">,</span> <span class="n">results</span><span class="o">]</span>
<span class="k">else</span>
<span class="n">file_proc</span><span class="o">.</span><span class="n">nil?</span> <span class="p">?</span> <span class="nb">Array</span><span class="o">.</span><span class="n">new</span> <span class="p">:</span> <span class="n">file_proc</span><span class="o">[</span><span class="n">top</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">if</span> <span class="bp">__FILE__</span> <span class="o">==</span> <span class="vg">$0</span>
<span class="nb">require</span> <span class="s2">"pp"</span>
<span class="k">unless</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">1</span> <span class="o">&&</span> <span class="no">File</span><span class="o">.</span><span class="n">exist?</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">first</span>
<span class="nb">abort</span> <span class="s2">"Usage: </span><span class="si">#{</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vg">$PROGRAM_NAME</span><span class="p">)</span><span class="si">}</span><span class="s2"> FILE_OR_DIRECTORY"</span>
<span class="k">end</span>
<span class="n">file</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="o">[</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">f</span><span class="p">),</span> <span class="no">File</span><span class="o">.</span><span class="n">size</span><span class="p">(</span><span class="n">f</span><span class="p">)</span><span class="o">]</span> <span class="p">}</span>
<span class="n">dir</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">d</span><span class="p">,</span> <span class="n">files</span><span class="o">|</span> <span class="o">[</span><span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">d</span><span class="p">),</span> <span class="no">Hash</span><span class="o">[*</span><span class="n">files</span><span class="o">]]</span> <span class="p">}</span>
<span class="n">pp</span> <span class="n">dir_walk</span><span class="p">(</span><span class="no">ARGV</span><span class="o">.</span><span class="n">first</span><span class="p">,</span> <span class="n">file</span><span class="p">,</span> <span class="n">dir</span><span class="p">)</span>
<span class="k">end</span>
</pre></div>
<p>The above makes me want for a multi-block syntax.</p>
<p>It's probably worth noting here that the above is basically an attempt at direct translation. I am aware of the standard <code>Find</code> library and I do think we should be using that, for this kind of traversal.</p>
<h4>Variable Scope</h4>
<p>Twice in this chapter, MJD has to pause the discussion to school the reader on the dangers of using improperly scoped variables, which can easily wreck recursion. What I found interesting in reading these was that coding the correct Perl was always adding a step, but it Ruby you have to add a step to get it wrong. What Rubyists naturally want to type turns out to be the correct thing to do, at least in these cases. I think this is a good sign that Ruby got variable scope right.</p>
<h4>Functional vs. OO Programming</h4>
<p>We always talk about how Ruby is very Object Oriented, but after reading this side discussion about the two approaches, I wonder how true that really is. The way MJD described OO, is certainly not how a lot of idiomatic Ruby comes out. And I found myself seeing a few of the described Functional Programming elements in my favorite language. Perhaps Ruby is a hybrid Functional OO Programming Language? Food for thought…</p>James Edward Gray II