This Is Why Groovy Rules

So I have a collection of newsletters, and each newsletter has a collection of articles.  Each article, in turn, has a collection of authors and a collection of categories.  Now what I need to do is get a list of unique handles for all the authors and categories for a given newsletter.  Here's the CFML version:

<cfset var article = "" />
<cfset var author = "" />
<cfset var category = "" />
<cfset var s = {} />
<cfloop array="#variables.newsletter.articles#" index="article">
  <cfloop array="#article.authors#" index="author">
    <cfset s[author.handle] = "" />
  <cfloop array="#article.categories#" index="category">
    <cfset s[category.handle] = "" />
<cfset variables.handles = structKeyArray(s) />

And here's the Groovy version:

variables.handles = variables.newsletter.articles.sum({
  it.authors.collect { it.handle } + it.categories.collect { it.handle }

I rest my case.

The use of addition ('+' operator and 'sum' method) to express appending collections together is very elegant, and coupled with 'collect' to transform each item in a collection, you get really concise and direct code.  The CFML also suffers from the lack of a Set data type, so you have to fake it with the keys of a structure.

8 responses to “This Is Why Groovy Rules”

  1. David McGuigan

    Is that something you'd really be doing in your app? Or is this kind of more of a demo of the coolness of those groovy functions?

    In my CFML app it'd just be:

    and done.

    Granted, I use a state-of-the-art configurationless, proprietary automatic service layer and utility framework, but still. That's how my everyday, out-of-the-box CFML would look for that operation. CFMLFTW.

  2. David McGuigan

    Oh shiz. Pwnd by the blog police. Let's try that again.

    nl = app.newsletters.get( url.idNewsletter )

    cfset handles = app.array( app.merge( app.unique( nl.authors, 'handle' ), app.unique( nl.categories, 'handle' ) ) )

    My snippet also includes the actual retrieval of the newsletter with its articles and categories collections as queries. Woot.

  3. David McGuigan

    Sorry, I have a distracting visitor. I totally mis-skimmed that. Real code:

    arts = app.newsletters.get( url.idNewsletter ).articles

    cfset handles = app.array( app.merge( app.unique( arts, 'authors.handle' ), app.unique( arts, 'categories.handle' ) ) )

    The unique calls return keyed structs ( or another format if you specify ), merge melts them together, and array intelligently casts any source collection as an array ( detects input format ). Pretty fun example. 3rd try's the charm.

  4. David McGuigan

    app is the shorthand I use for the application scope to accelerate readability ( thought that was obvious, sorry ), which I strategically pollute with the entire utility library as well as one instance of a magical, do-everything-imaginable object per database table ( what I call the superhero pattern ). I never even instantiate a service object, they're automatically omnipresent through ColdFusion's application scope on every request in my application, and are so tuned and refined that their startup as well as memory overhead are trivial.

    The only other line of code in the entire application to get that exact functionality and for that code to work is a
    in onApplicationStart. I literally have no configuration files or other setup necessary ( other than popping my decimateWorkload.cfc into the right folder on the server ).

    By storing each utility method ( and each superhero object ) in the application scope, everything is centralized, inspectable, performant, and trivial to learn and remember. Whether the creators of Groovy, ColdFusion, or I wrote the underlying library and algorithms a long time ago, the same end is still accomplished. The functionality is always available, with very little code.

    I realize it's a matter of taste ( and sometimes background ), but I totally ( respectfully ) disagree on your readability argument. I used to love methods as the properties of objects ( especially in my Javascript ), but I seriously prefer standalone-utility style syntax now, even without the performance benefits. It feels more natural and like more of a native extension of the language which to me at least feels very elegant and unified ( if only I could access application-scoped items implicitly, it would look just like part of the engine ).

    It opens up a really great opportunity to have more flexible, powerful, logically-accessed functions. My merge function can combine any number of objects of any type. An array and a list. A struct and an array. 4 arrays. Etc. My unique method can do many different things depending on what it receives. Etc. There's always an obvious and expected behavior depending on what you pass in. I really really love being able to call a guessably-named global utility for any utility-style operation and know that it'll respond intelligently. It feels magical, while logical. And most importantly super agile.

    To me having that sum method be a property of the articles collection seems weird and out of place. Not to mention it being named sum, which connotes math. Even weirder is that you're wrapping the + combination of two other method calls in what looks like object literal brackets inside the sum call ( I haven't done Groovy so I'm sure it clicks pretty quickly ). Which at a glance looks like you're passing an object literal with a single unnamed property ( the result of the call ).

    It's actually really interesting to me, and I've got "check out Groovy despite its name" on my to-do list. But for my taste, at least from a fresh-to-Groovy perspective, the way that syntax plays out ( though impressively concise ) is everything but readable.

    P.S. I love your blog content. You really hit a subset of CF-applicable topics that I don't see anywhere else. Props.

  5. David McGuigan

    I totally ignored your note, sorry.

    This is the only other line of code necessary for the previous code I wrote to work ( this goes in onApplicationStart ):

    createObject( "component", "decimateWorkload" ).wire( "nameOfDatasource" )

  6. Raffaele

    Just discovered CFGroovy2, and that's simply exciting!
    Can't wait to see more examples, maybe about calling coldfusion functions from groovy.

    good job!