My Secret Life as a Spaghetti Coder
home | about | contact | privacy statement
Base on the suggestion from Rob Wilkerson, I decided to pull this out into some code that illustrated the point I made in my last post. For context, see Another great use for closures.

Basically, what's going on is that we have a list() method that takes a query and dynamically outputs a list of its rows (with a heading column as well). It also has the ability to call a closure for a cell instead of doing its normal output, if one exists.

Most of the code below is tangential to the example, but as a whole, it illustrates the point I was trying to make, and shows a practical aspect of closures (using Sean Corfield's Closures for CFMX). In particular, there are a couple of methods of interest. Rather than passing a closure directly into the method, as one might normally do, I created a function, onOutputOf(), that puts the closures into a data structure to be checked by the method. This is only because it made more sense in my original application to do it that way, since the programmers are not typically calling the list() function themselves.

Next, there is a method to find a closure for a column if it exists. Rather than have the list function checking the closure storage data structure itself, I opted to use getClosureToBeRunOnColumn() to keep the methods as cohesive as I could see.

Of course, the list() method itself shows the use I found for this. And finally, callClosure() shows how I'm using Closures for CFMX, and the next to last line shows an example of passing in some code as a string.

If you have any questions (or comments on how I can improve this!) please feel free to let me know. I enjoy the feedback =). I'm done rambling now. Here's the code:

<!--- a simple utility function to make this example work --->
<cffunction name="queryRowToStruct">
   <cfargument name="query">
   <cfargument name="rowNum">
   <cfset var local=structNew()>
      <cfset local.result = structNew()>
      <cfloop query="arguments.query">
         <cfloop list="#arguments.query.columnList#" index="local.col">
            <cfset local.result[local.col] = arguments.query[local.col][arguments.rowNum]>
         </cfloop>
      </cfloop>
   <cfreturn local.result>
</cffunction>

<!--- set up a query for the example to use --->
<cfset beers = queryNew("beer,in_stock,rating","varchar,bit,integer")>
<cfset queryAddRow(beers,3)>
<cfset querySetCell(beers,"beer","Boddington's", 1)>
<cfset querySetCell(beers,"in_stock", true, 1)>
<cfset querySetCell(beers,"rating", 8, 1)>
<cfset querySetCell(beers,"beer","Newcastle", 2)>
<cfset querySetCell(beers,"in_stock", false, 2)>
<cfset querySetCell(beers,"rating", 9, 2)>
<cfset querySetCell(beers,"beer","Milwaukee's Beast", 3)>
<cfset querySetCell(beers,"in_stock", true, 3)>
<cfset querySetCell(beers,"rating", 1, 3)>

<cfset closuresToRunOnColumns = arrayNew(1)>
<!---
   this function takes all the closures to run on columns
   and stores them for later use.
--->

<cffunction name="onOutputOf" output="false">
   <cfargument name="column">
   <cfargument name="run">
   <cfset var singleClosureToRunOnColumn = structNew()>
   <cfset singleClosureToRunOnColumn.column = column>
   <cfset singleClosureToRunOnColumn.closure = run>
   <cfset arrayAppend(closuresToRunOnColumns, singleClosureToRunOnColumn)>
</cffunction>

<!---
   given a column name, this function returns the
   closure if it exists, "" otherwise
--->

<cffunction name="getClosureToBeRunOnColumn">
   <cfargument name="column">
   <cfset var local = structNew()>
   <cfset local.result = "">
   <cfloop from="1" to="#arrayLen(closuresToRunOnColumns)#" index="local.i">
      <cfif closuresToRunOnColumns[local.i].column eq column>
         <cfset local.result = closuresToRunOnColumns[local.i].closure>
         <cfbreak>
      </cfif>
   </cfloop>
   <cfreturn local.result>
</cffunction>

<!---
   this function creates and calls the closure so it is easy
   for the programmer to just inline his closures
--->

<cffunction name="callClosure">
   <cfargument name="closure_code">
   <cfargument name="row">
   <cfscript>
      closure_factory = createObject("component","org.corfield.closure.ClosureFactory");
      closure = closure_factory.new(closure_code).bind(outer=row);
      closure.call();
   </cfscript>
</cffunction>

<!--- this is our generic list function --->
<cffunction name="list" output="true">
   <cfargument name="query" required="true">
   <cfset var local = structNew()>
   <cfset local.columns = query.columnlist>
   <cfloop list = "#local.columns#" index="local.col">
      #local.col#,
   </cfloop>
   <br/>
   <cfloop query="arguments.query">
      <cfloop list="#local.columns#" index="local.col">
      <cfsavecontent variable="toBeOutput">
      <cfset local.closureToCall = getClosureToBeRunOnColumn(local.col)>
      <cfif local.closureToCall is "">
         <!--- if the column does not have a closure to run, output it --->
         #arguments.query[local.col][currentRow]#
      <cfelse>
         <!--- otherwise, call the closure --->
         <cfset callClosure(local.closureToCall,queryRowToStruct(arguments.query,currentRow))>
      </cfif>
      </cfsavecontent>
      #trim(toBeOutput)#,
      </cfloop>
      <br/>
   </cfloop>
</cffunction>

<!---
   Everything above this is hidden in our base class.
   Everything below is the stuff the end programmer
   needs to deal with.
--->


<!--- here's how the normal list looks --->
<cfset list(beers)>
<br/>
<!---
   But when the programmer calls it, he wants to change
   it. Instead of outputting 0/1 for in_stock, he wants
   it to say "yes" or "no"
   
   Also, he knows by documented convention that whatever variables
   are sent to his closure, they will be in a structure called
   "arguments"   
--->

<cfset onOutputOf(column="in_stock",run="<cfoutput>##yesNoFormat(outer.in_stock)##</cfoutput>")>
<cfset list(beers)>



Hey! Why don't you make your life easier and subscribe to the full post or short blurb RSS feed? I'm so confident you'll love my smelly pasta plate wisdom that I'm offering a no-strings-attached, lifetime money back guarantee!


Comments
Leave a comment

Slight fix to one of the comments: anything passed into the closure is not in the "arguments" scope in the code above. I've placed it into the "outer" scope instead. So the comment that says:

Also, he knows by documented convention that whatever variables are sent to his closure, they will be in a structure called "arguments"

should instead read:

Also, he knows by documented convention that whatever variables are sent to his closure, they will be in a structure called "outer"

Posted by Sam on Apr 28, 2007 at 03:34 PM UTC - 5 hrs

Not sure why you're using .bind() here - why not pass the row as an argument to .call() instead? .bind() is when you want to bind in a variable after creating the closure but then you pass the closure around and call it elsewhere (with arguments).

Also, in callClosure() you are creating a new factory and a new closure object on every row. You would do better to create the factory once for your application and then just create a closure instance once for each onOutputOf() call and store that in a struct (accessed by column name - you don't need an array: closuresToRunOnColumn[colName] = theClosure - then you don't need to loop through the array to retrieve a closure, just dereference the struct).

Posted by Sean Corfield on Apr 28, 2007 at 11:00 PM UTC - 5 hrs

Sean, believe it or not there is a good answer (not!) to your questions (well, sort of).

The short answer is I pulled this out of a real app trying to make it somewhat sensible without the rest of it, and didn't pay as much attention to detail as I should have. So, you're advice is good. (but, you knew that) =)

The longer explanation follows:

1) I was using bind() because (in the original application) I wanted to be able to make changes (in general, not just in the list function) to the outer struct and have them cascade outside the closure. I think that's the right call for it, but I welcome corrections (of course)

2) About instantiating the factory for each closure. I never thought about the repercussions in the example. Clearly, as I've shown it in the example is not the right thing to do. I never even noticed when I put it inside there (in fact, in the real code, the callClosure() method looks more like closure_factory.new(closure_code).bind(outer=row).call()
(not looking at it, just going from memory)

3) Finally, using the array as a data structure to hold this in was just retarded on my part. It was some combination of "I've been using arrays to store my stacks of functions to run" (because they are called before the object is fully instantiated) and "oh, linear time to find out if there is a closure to call or not is good enough."

You'll be glad to know that earlier today I already migrated away from that and used the constant time hashes as you suggested.

Thanks for the insights as well. I need them =)

Posted by Sam on Apr 28, 2007 at 11:43 PM UTC - 5 hrs

Re: 1. and 2. - .bind(name=object).call() is essentially the same as .call(name=object) so you never need .bind() if you are calling the closure in the *same* context.

You only need .bind() if you are binding variables in one context and calling them in another context.

The whole .bind(variable_bindings) / .call(argument_bindings) thing is what gives closures their power but it's also the hardest part to "get".

Posted by Sean Corfield on Apr 29, 2007 at 06:46 AM UTC - 5 hrs

You were up early (or late?) =)

Thanks for that last clarification. I see your point now, so I'll be changing that to pass in via an argument to the call method instead of the bind method.

Really, it does make more sense that way I think.

Posted by Sam on Apr 29, 2007 at 09:30 AM UTC - 5 hrs

Leave a comment

Leave this field empty
Your Name
Email (not displayed, more info?)
Website

Comment:

Subcribe to this comment thread
Remember my details
Google
Web CodeOdor.com

Me
Picture of me

Topics
.NET (19)
AI/Machine Learning (14)
Answers To 100 Interview Questions (10)
Bioinformatics (2)
Business (1)
C and C++ (6)
cfrails (22)
ColdFusion (78)
Customer Relations (15)
Databases (3)
DRY (18)
DSLs (11)
Future Tech (5)
Games (5)
Groovy/Grails (8)
Hardware (1)
IDEs (9)
Java (38)
JavaScript (4)
Linux (2)
Lisp (1)
Mac OS (4)
Management (15)
MediaServerX (1)
Miscellany (76)
OOAD (37)
Productivity (11)
Programming (168)
Programming Quotables (9)
Rails (31)
Ruby (67)
Save Your Job (58)
scriptaGulous (4)
Software Development Process (23)
TDD (41)
TDDing xorblog (6)
Tools (5)
Web Development (8)
Windows (1)
With (1)
YAGNI (10)

Resources
Agile Manifesto & Principles
Principles Of OOD
ColdFusion
CFUnit
Ruby
Ruby on Rails
JUnit



RSS 2.0: Full Post | Short Blurb
Subscribe by email:

Delivered by FeedBurner