Note that there are some explanatory texts on larger screens.

plurals
  1. POIdiomatic MetaProgramming
    primarykey
    data
    text
    <p>While the web abounds with resources extolling the myriad metaprogramming capabilities of Groovy, I've yet to find anything close to a comprehensive "best-practices" guide for the actual use of such features.</p> <p>Aside from the typical <em>caveat emptor</em> warning of excessive usage, the most specific piece of advice I've read suggests using categories in favor of augmenting metaclasses when possible (which is really just another way of reinforcing the old idiom of 'limited scope').</p> <p>Common sense has been sufficient for my trivial projects, but I'm increasingly concerned about building from potentially poor / inconsistent precedents as I tackle more ambitious tasks.</p> <p>Thus, I would greatly appreciate any advice, resources, or concrete examples of Groovy (or even language-agnostic - My admittedly brief experience with Ruby left me similarly wanting) metaprogramming best-practices.</p> <p>To clarify the topic, I'll provide a (highly) simplified Rational number project that could employ metaprogramming in several different ways:</p> <pre><code>@Immutable class Rational{ int num, den Rational multiply(Integer v){ new Rational(num:num*v, den:den) } } assert new Rational(num:1, den:2) * 3 == new Rational(num:3, den:2) </code></pre> <p>Yet, attempting <code>3*new Rational(num:1, den:2)</code> would obviously produce a MissingMethodException.</p> <p>The simplest, and arguably least fragile means of adding the communicative property would be with a static initializer block in the Rational class:</p> <pre><code>static { Integer.metaClass.multiply = {Rational fraction -&gt; fraction*delegate} } ... assert 3*new Rational(num:1, den:2) == new Rational(num:3, den:2) </code></pre> <p>But this is consequently global in effect, and rather rigid.</p> <p>A more versatile, and perhaps more organized approach would be with some kind of optional bootstrapping:</p> <pre><code>class BootStrap{ static void initialize(){ Integer.metaClass.multiply = {Rational fraction -&gt; fraction*delegate} } } </code></pre> <p>Now we have the option of enabling the feature(s) we wish to have. This, however, could result in all manner of dependency issues.</p> <p>And then there's categories.. Safely explicit, but not exactly convenient:</p> <pre><code>@Category(Integer) class RationalCategory{ Rational multiply(Rational frac){ frac*this } } use(RationalCategory){ assert 3*new Rational(num:1, den:2) == new Rational(num:3, den:2) } </code></pre> <p>In general, I find I modify metaclasses when I'm <em>adding</em> new behavior, but use categories when I may be <em>changing</em> existing behavior. Overriding the division operator to produce a Fraction, for instance, would be best contained inside a category. Thus, solutions 1 or 2 would be 'acceptable' as I'm merely appending behavior to the Integer class, not altering typical usage.</p> <p>Does anyone agree/disagree with that sentiment? Or perhaps know of some superior methodology? (I omitted mixins here, I realize.)</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. COI think a lot depends on context. Is your meta method useful globally or only in specific contexts. I've found my biggest problem is remembering where a meta method was added and if you have it in your current context. There have been many times where I used it as a category and then realized it was a better fit globally in the app and added it there as part of a refactor.
      singulars
    2. CO@dstarh That makes sense - It was part of my motivation for listing the in-place vs bootstrapping methodologies. I would think consolidating metamethods to a bootstrap class would be the most organized approach. I could be wrong, but I believe that's the approach typically taken in Grails. It gives you control of what gets added and when. I just don't like the fragility factor, nor complicating a build procedure any more than I must. Starting with categories if but because they are easier to refactor is certainly a logical approach.
      singulars
    3. COi agree that it makes the most sense in a lot of cases to do that It's prevented interesting problems like when another developer is wondering where the hell the .toUrl() method gets added to the String class. if you centralize them in one location it's not as confusing. Especially when you can pass a class to another class where the referenced class had a meta method added to it somewhere up the call stack and then it's used for example in the view.
      singulars
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload