An Architect's View

CFML, Clojure, Software Design, Frameworks and more...

An Architect's View

Closures for ColdFusion

October 8, 2006 ·

I was having a discussion about closures with someone recently and I said they wouldn't make sense for ColdFusion because the syntax would be too ugly. On Friday evening, I decided to try to prove myself wrong. You can download my Closures for CFMX library from the "Software" pod on my blog. Read the comments in the index.cfm file for details on how to use it. Here's one example from that file:
<cfset doubler = cf.new("multiplier * arguments.n").bind(multiplier=2) />
<p>doubler.call(n=21) = #doubler.call(n=21)#</p>
<cfset calc = cf.new("multiplier * n","n") />
<cfset calc.name("do") />
<cfset triple = calc.bind(multiplier=3) />
<p>triple.do(14) = #triple.do(14)#</p>
You'll get 42 each time. No idea what anyone might want to use this for but it's "cool". Enjoy!
Peter Bell added a few comments including a link to Martin Fowler talking about closures. I figured I'd add an example showing how you might do similar things in ColdFusion with this library. First off, you need a select function to match Ruby:
<cfscript>
function select(collection,closure) {
   var result = structNew();
   var e = 0;
   for (e in collection) {
      if (closure.call(collection[e])) {
         result[e] = collection[e];
      }
   }
   return result;
}
</cfscript>
Now we'll build a collection (a struct) containing some simple struct data elements:
<cfset emps = structNew() />
<cfset e = structNew() />
<cfset e.name = "Sean" />
<cfset e.isManager = true />
<cfset emps[e.name] = e />
<cfset e = structNew() />
<cfset e.name = "Matias" />
<cfset e.isManager = false />
<cfset emps[e.name] = e />
<cfset e = structNew() />
<cfset e.name = "Paul" />
<cfset e.isManager = false />
<cfset emps[e.name] = e />
Finally, we run the Ruby-like select method:
<cfset mgrs = select(emps,cf.new("e.isManager","e")) />
We create the closure to match Ruby's code block { |e| e.isManager } - we name the argument (the second argument to the new() method so it can be called without having to used named arguments - as the select() method expects.

Tags: coldfusion · oss

19 responses

  • 1 Jared Rypka-Hauer // Oct 8, 2006 at 4:54 PM

    That is cool... but... yeah.

    What for, other than cool? Maybe Peter Bell can use this for his application generation framework. ;)

    JUST TEASING PETER! :D

    J
  • 2 Peter Bell // Oct 8, 2006 at 9:23 PM

    @Sean - very cool! Will keep reading your sample and will play with your code to try to figure out what this is all about.

    @Jared - NP at all! I've been reading up on closures on and off for weeks now. Now at least I have some sample CF code so if I can just think of something to use this for I'll be set!

    Truth be told, I still don't really &quot;get&quot; closures, but I have a book on Haskell with my name on it and I'm reading a lot of Ruby code, so I'll be sure to let you know if I figure out what this could be used for.

    Like Mixins, I'm sure there's gold in tham thar patterns . . .

    @everyone - if anyone thinks up some interesting use cases for closures in CF I'd love to hear them!
  • 3 Ken Dunnington // Oct 9, 2006 at 6:50 AM

    Now those JavaScript snobs can't say that ColdFusion isn't a &quot;real&quot; language because it can't do closures! Er, not that anyone ever said that (I hope) but nice job nonetheless, Sean. :) How long did it take you to throw this together?
  • 4 Peter Bell // Oct 9, 2006 at 7:08 AM

    Oops, The fowler article is at
    http://www.martinfowler.com/bliki/Closure.html
  • 5 Sean Corfield // Oct 9, 2006 at 8:17 AM

    Peter, it took about an hour to design the machinery (notes I wrote in OpenOffice, mostly on my BART commute) and about an hour to write and test the code, then about another hour to document it: adding hint= attributes and comments to the index.cfm file.

    The Ruby examples Fowler gives assume collections of objects and a select function. Select takes a closure and returns the elements of the collection for which applying the closure's method returns true.

    I'll edit my entry to show how that might work in ColdFusion with this closure library.
  • 6 Peter Bell // Oct 9, 2006 at 8:22 AM

    Sean, Looking forwards to it. Funny that you did this in three hours. I always seem to find that coding something takes very little time - a couple of hours here or there. It is learning the patterns and understanding how and where to best apply them that takes days of pondering! LightWire was about 4 hours of coding end to end for a setter based DI engine!
  • 7 Barney // Oct 9, 2006 at 8:53 AM

    You can do very close to this already:

    function select(collection, closure) {
    var result = structNew();
    var e = &quot;&quot;;
    for (e in collection) {
    if (closure(collection[e])) {
    result[e] = collection[e];
    }
    }
    return result;
    }
    function test(e) {
    return e.isManager;
    }

    select(emps, test);

    The only thing missing is the function literal in the call to select (you have to predefine it). If CF would just support functions as a &quot;real&quot; datatype (including a literal syntax), then you'd have it all, without any extra weirdness.

    Of course, you'd want the scoping rules of a closure as well, but I assume that your CFC's don't provide that? It would be remarkably inefficient to do so, I'd think.
  • 8 Sean Corfield // Oct 9, 2006 at 9:40 AM

    Barney, yes, but if the test was e.empStatus = x where x was a variable in the function calling select, you couldn't do it easily.

    cf.new(&quot;return e.empStatus = statValue;&quot;,&quot;e&quot;).bind(statValue=x)

    That creates a closure with statValue bound to the local x variable in the calling code...
  • 9 Barney // Oct 9, 2006 at 11:45 AM

    Yes, I saw that after some more review of the code. Though I must say, the manual binding is quite a price to pay, and could be the cause of some very subtle bugs for those used to programming with &quot;normal&quot; closures. For example, pass-by-value types could result in unintended if the bound variable is modified after the bind() invocation. In Javascript you'll always get the last-set value (even if it's set after the closure is defined). Java's &quot;closures&quot; prevent ambiguity by forcing you to declare variables that the closure will use as final (preventing modification).

    Which isn't to say this isn't useful, of course, just that there are some gotchas to be aware of.
  • 10 Sean Corfield // Oct 9, 2006 at 11:59 AM

    Yes, there are some gotchas - that's partly why I've posted this, to get feedback from folks. If there's enough interest, I can make this into a more robust open source project and solicit feature requests.

    Currently, when you bind a value type, the closure keeps that value (even if the variable is subsequently changed); when you bind a reference type, the closure keeps the reference but picks up live changes. Also, the closure itself has &quot;history&quot; in that changes to bound variables inside the closure affect future calls to the same closure instance (this is actually useful since you can write an aggregator with a closure).

    That may indeed cause surprises. I considered machinery to perform a deep copy of bound variables but decided it was too much overhead for a proof of concept.

    I'll be interested to see what sort of feedback people have once they start trying to use this in the wild...
  • 11 Peter Bell // Oct 9, 2006 at 1:31 PM

    I'm still having a hard time getting my head around the use cases for closures. I've Googled this a bit, but was getting even more confused.

    Could you suggest some starting points (as with mixins which now make all the sense in the world to me) for experimentation?

    If you're building a cms, writing a DAO or service or controller, creating a shopping cart or write a newsletter system where is a time that a closure would help you to solve a problem more flexibly or elegantly than just having methods within a class.

    About the only thing that has penetrated for me is that you can dynamically create a function that has certain cool properties in terms of its existance and relationship to or access to the caller scope or something. Any thoughts appreciated, but more importantly, I'm voting for this to be a project if you have the time and will definitely take the time to learn enough about closures to take advantage of anything you create!



  • 12 Mark Mandel // Oct 9, 2006 at 5:53 PM

    I'm actually wondering if this sort of thing would be handy for something like Transfer - where you could create select and/or sort functions for collections of composite child objects.

    That way it could be very easy to filter out objects from a group, or resort them in a custom way that Transfer doesn't automatically handle internally.
  • 13 Sean Corfield // Oct 9, 2006 at 7:53 PM

    Peter, yeah, it's hard to give examples off the top of my head... When I've worked in languages with native support for closures, you just tend to use them naturally. Fowler's post on closures gives some good examples.

    I'd have to look through my old ColdFusion code to see if I can come up with some examples that would be made simpler by the use of closures.

    Mark may be on the ball with using closures to create reusable ad hoc queries... definitely a way to create filters etc on the fly.
  • 14 Tom Chiverton // Oct 10, 2006 at 1:44 AM

    The code for doing anything using Closures just looks sick compaired to doing it the way we're all used to. And I see no benefit.
    This has implications for someone else coming along and looking at your code.
  • 15 Sean Corfield // Oct 10, 2006 at 7:51 AM

    @Tom, ColdFusion does not support closures natively so could you explain what you mean by &quot;doing it the way we're all used to&quot;?
  • 16 Peter Bell // Oct 10, 2006 at 8:04 AM

    And Tom,

    ANY new pattern requires a learning curve on the part of other developers reading your code. Think of how hard it is for a procedural CF developer to make sense of a well architected application using CFCs. Does that mean CFC's are wrong?!

    The question is whether the additional learning pays for itself by allowing you to solve problems more elegantly. For en example, look at my LightWire code as a good example of Object based mixins. You have an entire DI/IoC engine including the resolution of circular dependencies in under 200 lines of code (including a couple of required base methods). In many ways it is an inferior approach when compared to ColdSpring, but ColdSpring is a little more than 200 lines of code! Using object based mixins allows DI to be simplified to the point where anyone can &quot;get it&quot; and indeed anyone can just &quot;write it themselves&quot; if they have a good reason to.

    Mixins can be confusing when you first see them, but they may repay the study if you have the kind of problem they can be used for (despite all of the usual caveats about losing the self documenting nature of the class files).

    I'm pretty sure closures are the same and am interested in Marks ideas above and to hear if anything concrete comes to Sean.

  • 17 Tom Chiverton // Oct 11, 2006 at 1:55 AM

    @Sean: &quot;the way we're all used too&quot; means writing a CFC or UDF and calling that.

    @Peter: I'm not against learning something new, if someone can make the case it is worthwhile (CFCs over UDF for instance). I'm not seeing closures as offering anything. Maybe as Sean said it was a bad example.
  • 18 Sammy Larbi // Dec 16, 2006 at 2:01 PM

    Sean, just got a copy of this and it looks cool. Some trouble I had with this and CF 6.1 was that expandpath (around line 72 in the ClosureFactory.cfc) is giving a different path than &lt;cfinclude &quot;curDir&quot;&gt; is using. Therefore, I would need to put the CFC in the current directory, or else put my code in org/corfield/closure. Anyway, I changed that line to &lt;cfset filePath = getDirectoryFromPath(getcurrenttemplatepath()) &amp; &quot;&quot; &amp; functionName /&gt; and all works well.
  • 19 Sean Corfield // Dec 16, 2006 at 5:47 PM

    Sammy, if you are on Windows, expandPath() does not work properly. It's fixed in CFMX 7 (and it worked in Mac / *nix versions in CFMX 6.1).

    Glad to hear you got it working.