Gray Soft / Tags / DSLstag:graysoftinc.com,2014-03-20:/tags/DSLs2014-04-11T19:14:19ZJames Edward Gray IIDSL Block Stylestag:graysoftinc.com,2008-10-07:/posts/602014-04-11T19:14:19ZThere are two different ways to handle a DSL using Ruby's blocks but you don't really have to choose.<p>There's an argument that rages in the Ruby camps: to <code>instance_eval()</code> or not to <code>instance_eval()</code>. Most often this argument is triggered by DSL discussions where we tend to want code like:</p>
<div class="highlight highlight-ruby"><pre><span class="n">configurable</span><span class="o">.</span><span class="n">config</span> <span class="k">do</span>
<span class="n">width</span> <span class="mi">100</span>
<span class="n">mode</span> <span class="ss">:wrap</span>
<span class="k">end</span>
</pre></div>
<p>You can accomplish something like this by passing the block to <code>instance_eval()</code> and changing <code>self</code> to an object that defines the <code>width()</code> and <code>mode()</code> methods. Of course changing <code>self</code> is always dangerous. We may have already been inside an object and planning to use methods from that namespace:</p>
<div class="highlight highlight-ruby"><pre><span class="k">class</span> <span class="nc">MyObject</span>
<span class="kp">include</span> <span class="no">Configurable</span> <span class="c1"># to get the config() method shown above</span>
<span class="k">def</span> <span class="nf">initialize</span>
<span class="n">config</span> <span class="k">do</span>
<span class="n">width</span> <span class="n">calculate_width</span> <span class="c1"># a problem: may not work with instance_eval()</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">calculate_width</span> <span class="c1"># the method we want to use</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>In this example, if <code>width()</code> comes from a different configuration object, we're in trouble. The <code>instance_eval()</code> will shift the focus away from our <code>MyObject</code> instance and we will get a <code>NoMethodError</code> when we try to call <code>calculate_width()</code>. This may prevent us from being able to use <code>Configurable</code> in our code.</p>
<p>A common solution is to pass the object with the <code>width()</code> and <code>mode()</code> methods into the block. You can then make calls on that object and keep the same <code>self</code>. This could fix the above problem example:</p>
<div class="highlight highlight-ruby"><pre><span class="n">config</span> <span class="k">do</span> <span class="o">|</span><span class="n">c</span><span class="o">|</span>
<span class="n">c</span><span class="o">.</span><span class="n">width</span> <span class="n">calculate_width</span>
<span class="k">end</span>
</pre></div>
<p>I imagine most of us agree that's not quite as smooth, but it tends to get viewed as a necessary evil. It's just not safe to always use <code>instance_eval()</code>. I think there are some issues with that line of thinking though:</p>
<ul>
<li>Sometimes <code>instance_eval()</code> is OK and we would prefer to have the prettier syntax when it is</li>
<li>Library authors are making this blanket decision for all the use cases</li>
<li>We have a super dynamic language here so we should be able to have it both ways</li>
</ul><p>It turns out that we can accommodate both schools of thought rather easily. You can ask Ruby to bundle up any block into a <code>Proc</code> object and <code>Proc</code> objects have an <code>arity()</code> method that will tell you how many arguments they expect. We can use that to determine when to switch strategies:</p>
<div class="highlight highlight-ruby"><pre><span class="k">class</span> <span class="nc">DSL</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="o">&</span><span class="n">dsl_code</span><span class="p">)</span> <span class="c1"># creates the Proc</span>
<span class="k">if</span> <span class="n">dsl_code</span><span class="o">.</span><span class="n">arity</span> <span class="o">==</span> <span class="mi">1</span> <span class="c1"># the arity() check</span>
<span class="n">dsl_code</span><span class="o">[</span><span class="nb">self</span><span class="o">]</span> <span class="c1"># argument expected, pass the object</span>
<span class="k">else</span>
<span class="nb">instance_eval</span><span class="p">(</span><span class="o">&</span><span class="n">dsl_code</span><span class="p">)</span> <span class="c1"># no argument, use instance_eval()</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">do_something</span>
<span class="nb">puts</span> <span class="s2">"Doing something..."</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="no">DSL</span><span class="o">.</span><span class="n">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">d</span><span class="o">|</span> <span class="n">d</span><span class="o">.</span><span class="n">do_something</span> <span class="p">}</span>
<span class="c1"># >> Doing something...</span>
<span class="no">DSL</span><span class="o">.</span><span class="n">new</span> <span class="p">{</span> <span class="n">do_something</span> <span class="p">}</span>
<span class="c1"># >> Doing something... </span>
</pre></div>
<p>Users of this code can now decide how they want it to work based on their needs as the examples show.</p>
<p>With a language like Ruby these limitations just become opportunities for showing off how dynamic our code can be. Don't be so quick to give in to necessary evils.</p>James Edward Gray II