I was adding a feature to CommandBox CLI this week when I typed up some code that iterated over the keys in a struct, filtering out the ones it needed and then performed an action on the matching ones.  I used the functional methods structFilter() and structEach() in CFML.  They are an example of functional programming as they accept functions as input.  This also means we can call them "higher order" functions.  But termininologo aside, I typed up the same code using an older interactive approach just to compare the two.  It was an interesting and rather self contained example so I thought I'd share it as a real life use case for FP (functional programming).

This requirement for this code was simply to take a struct of incoming arguments, filter out any called "scriptName" and create a CLI environment variable for the remaining key/value pairs.

The "old" way

Here is the way I'd write this code previously in CFML:

for( var key in arguments ) {
    if(  key != 'scriptName' ) {
        systemSettings.setSystemSetting( key, arguments[ key ] );    
    }    
}

Let's take some notes on this:

  • This style tells the compiler HOW to accomplish the task (loop, set variables, check conditions, etc)
  • This style requires me to handle the looping mechanics
  • This style requires the use of temporary variables to track the loop (which live past the scope of this code)
  • This particular example doesn't modify the arguments struct, but it also doesn't promise not to

The "functional" way

Here's the way I actually wrote the code this week:

arguments
    .filter( ( k, v ) => k != 'scriptName' )
    .each( ( k, v ) => systemSettings.setSystemSetting( k, v ) );

Let's take some notes on this as well:

  • The style tells the compiler WHAT to do, not how to do it (filter the struct, then run something for each item, etc)
  • This style uses closures to encapsulate the action to be taken against each struct element
  • This style has no manual looping
  • This style leaves no local variables littered in memory to track the loops (k, and v are in the arguments scope of each closure)
  • This style, by definition, will leave the arguments struct unchanged as the structFilter() method will return a new struct
  • I also didn't need to dereference the value in the struct since that was conveniently passed to the closure as "v".

Another functional way

Ok, so both examples are small and easy to read.  You may be thrown off a little by the "arrow function" syntax I used in the second example.  Adobe ColdFusion doesn't support this still, even though they marked the ticket for it "fixed" before the pre-release.  I honestly have no idea what's going on there.  Lucee has supported this for quite some time and it's really just a way to strip out as much boilerplate as possible from a closure.  It's a little wonky at first for people who are used to all the extra curlies and keywords, but the more you see it, the easier it is to read.  Basically, the "function" keyword is missing as is the return statement in the filter (the last expression is automatically returned) as well as the curlies for the function body since both closures only contain a single statement.  If that second example is a little hard on your eyes, here's a more familiar version of the exact same code that will also work in Adobe ColdFusion:

arguments
    .filter( function( k, v ) {
      return k != 'scriptName';
    } )
    .each( function( k, v ) {
      systemSettings.setSystemSetting( k, v );
    } );

Conclusion

So I think all the points I really wanted to make are in the bullets above.  Both versions are equally valid and produce the same behavior, but once you get used to telling the compiler WHAT to do and not HOW to do it, it really starts to clean up your code.  Gone are "off-by-one" looping errors, nasty side effects on the original data structures (immutability), and reading the code has a more linear flow to me since I don't need to unwrap the loops and if statements in my mind to figure out what it's doing.  

Extra Credit

If you're exceptionally clever, you may have noticed that the each() function passes the same arguments the closure receives directly to the setSystemSetting() function and you may have thought, WAIT A MINUTE-- can't that be simplified even more down to just this:

arguments
	.filter( ( k, v ) => k != 'scriptName' )
	.each( systemSettings.setSystemSetting );

In that example we forego the closure entirely and just pass the systemSetting UDF in directly as a reference (note there's no () since I'm not executing it, just passing a reference to it).  This WOULD work, however in our case, ColdFusion/Lucee passes a 3rd positional argument which is a reference to the original struct AND setSystemSettings() also accepts a 3rd, optional argument which gets screwed up when it receives in the struct reference instead.  So, if you thought that, then good thinking, but in this specific case I couldn't get away with it :)