Skip to main content
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Rick Stumbo
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Rick Stumbo

Variadic Functions Considered Through "Worse Is Better" In ColdFusion

By
Published in

Yesterday, I took a look at the mechanics of writing a variadic function in ColdFusion. A variadic function, like console.log(), is a function that takes an indeterminate number of arguments. I love the idea of variadic functions; but, I don't actually write them very often; and, I believe that much of this disconnect stems from the fact that I don't have a good decision tree for when a variadic function is the "right" choice. Recently, however, I listened to the Future of Coding episode about the "Worse is Better" school of thought; and I found it very relevant to variadic functions.

As I understood the explanation on the podcast, the Worse is Better philosophy pertains to a certain set of trade-offs. Specifically a desire to create simplicity of consumption even if it means an increased complexity of implementation and possibly even a lack of completeness.

Given this set of trade-offs, I can now ask myself two questions when considering a variadic function signature:

  1. Am I making this choice because it's easier for the user (developer) to consume? Or, because it will be easier to code internally to the function?

  2. Am I making this choice because I'm solving a problem that the user (developer) has right now? Or because I'm worried about future problems I may one day encounter?

While I think the first question comes into play some of the the time, I think the second question is really the one that keeps me from creating more variadic functions. I end up doing a lot of hand-wringing over possible future problems that a variadic function signature may not be able to handle easily.

Consider a utility function for creating internal links in a ColdFusion application. I might need to handle a variety of configuration options:

  • The script to be called.
  • The action to be processed.
  • The search parameters to append.
  • The possible fragment to be applied on the target page.

If I start off by using "The Right Thing" school of thought—the opposite of Worse is Better—I'd probably end up creating a function that depends on named parameters in which each of the aforementioned settings could be passed-in. For example:

<cfscript>
urlFor(
scriptName = "/index.cfm",
event = "user.account",
searchParameters = [
returnTo: "home"
],
fragment = "account"
);
</cfscript>
view raw snippet-1.cfm hosted with ❤ by GitHub

This function signature is robust and complete; and, is flexible - if I ever need to add another option, it can be easily added as an additional named argument without the possibility of breaking any of the existing invocations.

But, this level of clarity, completeness, and robustness isn't free. It comes at the cost of simplicity and pushes the complexity onto the consumer.

Consider the fact that the scriptName will be index.cfm is 99% of all routes. And, consider the fact that all routes in the application must have an event to be meaningful. And consider the fact that all search parameters are name-value pairs. And consider that the majority of URLs probably won't have a fragment. If taken together, all of these considerations can lead us to a "Worse is Better" variadic function in which:

  • The assumed script name is index.cfm.
  • The first argument is implicitly the event.
  • Every N and N+1 argument represents a search parameter.
  • Any remaining argument is the fragment.

With a variadic function like this, the previous call with named arguments becomes the following call with ordered arguments:

<cfscript>
urlFor( "user.account", "returnTo", "home", "account" );
</cfscript>
view raw snippet-2.cfm hosted with ❤ by GitHub

Is this method call less clear? Yes. At least, it is the first time you see it. But, once you understand what it is and what it's doing, every subsequent read and invocation becomes greatly simplified.

Here's what an implementation of this varaidic function might look like in ColdFusion:

<cfscript>
dump( urlFor( "auth.logout" ) );
dump( urlFor( "user.account", "userID", 4, "returnTo", "home", "account" ) );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I generate an internal URL for the given inputs.
*/
public string function urlFor( required string event /* ...params */ ) {
var inputs = arrayMap( arguments, ( element ) => element );
var searchParams = [];
var fragment = "";
// We know that the first argument is always the routing event.
searchParams.append( "event=" & encodeForUrl( inputs.shift() ) );
// After the event, we're going to assume a series of key-value pairs using
// sibling arguments. As such, we're going to shift 2 values at a time.
while ( inputs.isDefined( 2 ) ) {
searchParams.append(
encodeForUrl( inputs.shift() ) & "=" &
encodeForUrl( inputs.shift() )
);
}
// If there's anything left, we're going to assume it's the fragment / hash.
if ( inputs.isDefined( 1 ) ) {
fragment = "###encodeForUrl( inputs.shift() )#";
}
return "/index.cfm?#searchParams.toList( '&' )##fragment#";
}
</cfscript>
view raw test2.cfm hosted with ❤ by GitHub

When we run this ColdFusion code, we get the following output:

/index.cfm?event=auth.logout
/index.cfm?event=user.account&userID=4&returnTo=home#account

In the above example, the "Worse is Better" approach is clearly a trade-off. Meaning, changing it from an named-argument invocation to a ordered-argument invocation clearly has both benefits and draw-backs. But, this distinction isn't always clear.

Consider a variadic function that allows a list of CSS class names to be compiled based on various conditions. These class names might be:

  • Static.
  • Dynamic based on certain conditions.
  • Passed-in from another context.

Compiling these cases together implies a messy madness that is the reality of the calling context. Attempting to bring order to this chaos - to create a "Right Thing" solution - might actually make the function less intuitive because you'd be trying to apply names to things aren't uniquely distinct.

In this case, a "Worse is Better" solution, using a variadic function aligns with both the messiness of class name compilation and with the desire for simplicity. A variadic function for this might accept several different types of inputs:

  • Strings.
  • Arrays of other inputs.
  • Struct of key-value pairs in which the key is the class name to apply and the value is the condition that determines whether or not to apply said class.

Consider a UI of tabbed navigation in which every tab has the class tab-item but only one of the tabs will have the class, is-active. A variadic function with mixed inputs might look like this:

<cfscript>
classNames( "tab-item", { "is-active": true } );
</cfscript>
view raw snippet-3.cfm hosted with ❤ by GitHub

Again, we're in a situation where the very first time you see this function call, it might be confusing. But, once you know how it works, the simplicity of the call becomes a major plus.

Here's what this variadic function might look like in ColdFusion:

<cfscript>
// The variadic nature of the class names function allows it to be called with very
// simple arguments and no ceremony:
dump( classNames( "tab-item" ) );
// ... as well as slightly more complicated and dynamic arguments.
dump( classNames( "tab-item", { "is-active": true } ) );
// ... and even very complicated and dynamic arguments.
dump(
classNames(
"foo",
"bar",
[
"hello",
"world",
[
"deeply",
"nested",
{
woot: true,
toot: false
}
]
],
{
truthy: true,
falsy: false
}
)
);
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I built of a list of CSS class names from the given list of conditions.
*/
public string function classNames( /* ...conditions */ ) {
var conditions = arrayMap( arguments, ( element ) => element );
var values = [];
for ( var condition in conditions ) {
// Add simple values as-is to the list of values.
if ( isSimpleValue( condition ) ) {
values.append( condition );
// Merge arrays, recursively, into the list of values.
} else if ( isArray( condition ) ) {
values.append( classNames( argumentCollection = condition ), true );
// Add struct keys when struct value is a truthy.
} else if ( isStruct( condition ) ) {
for ( var name in condition ) {
if ( condition[ name ] ) {
values.append( name );
}
}
}
}
return values.toList( " " );
}
</cfscript>
view raw test.cfm hosted with ❤ by GitHub

Notice that this method calls itself recursively when one of the inputs is an array. This allows for a high level of flexibility when munging a bunch of class names together. And, it likely does so at the cost of performance. But, again, the trade-off is that it makes the calling context much simpler.

When we run the above ColdFusion code, we get the following output:

tab-item
tab-item is-active
foo bar hello world deeply nested woot truthy

When it comes to writing variadic functions in ColdFusion, there's no simple answer. But, I like that the healthy tension between the "Worse is Better" and the "Right Thing" schools of thought give me tools to help make my decision process more explicit. Is completeness more important? Or, is simplicity more important? Again, there's no right answer; but, at least I have more of framework in which to consider the trade-offs.

Want to use code from this post? Check out the license.

Reader Comments

Post A Comment — I'd Love To Hear From You!

Markdown formatting: Basic formatting is supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.
Cancel
I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel