Required Function Arguments Don't Have To Come First In ColdFusion
For my entire career, when defining a Function or Method that accepts both required and optional arguments, I've always put the required arguments first and the optional arguments second. In the vast majority of function signatures, this works perfectly well. But every now and then, I have a function signature whose arguments don't neatly align with the required/optional semantic ordering.
Consider a function that formats a phone number. We often think of a phone number as a single value; but this value is a composite of smaller atomic parts:
- Exit code.
- Country code.
- Area code.
- Telephone prefix.
- Line number.
Of these atomic parts, the only ones that are required are the last two. Which means that if I wanted to strictly follow the rule of having all required arguments come first, the argument list in this method signature might look like this:
- Telephone prefix. (required)
- Line number. (required)
- Exit code.
- Country code.
- Area code.
This would work; but, semantically, it would read like an incoherent understanding of how phone numbers are constructed.
In ColdFusion, we don't have to follow this strategy mindlessly. In ColdFusion, we have two ways of invoking a Function or Method: with ordered parameters or named parameters. Many functions work well with both invocation styles. But, some functions really only make sense to invoke with named parameters. Formatting a complex phone number is one of these cases.
By assuming that a function will only ever be invoked with named parameters, it doesn't matter — mechanically — where the required arguments sit within the list of parameters. Which means that we can order the arguments using a natural, semantic order:
- Exit code.
- Country code.
- Area code.
- Telephone prefix. (required)
- Line number. (required)
Here's an example of this ColdFusion code in action — note that the formatting logic here isn't intended to be perfect, it's just an example of how to define the function signature.
<cfscript>
a = phoneFormat(
telephonePrefix = "555", // Required argument.
lineNumber = "6767" // Required argument.
);
b = phoneFormat(
countryCode = "1",
areaCode = "212",
telephonePrefix = "555", // Required argument.
lineNumber = "6767" // Required argument.
);
c = phoneFormat(
exitCode = "011",
countryCode = "39",
areaCode = "99",
telephonePrefix = "555", // Required argument.
lineNumber = "6767" // Required argument.
);
writeOutput( "#a# <br>" );
writeOutput( "#b# <br>" );
writeOutput( "#c# <br>" );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I return a single phone number string using the given components.
*/
private string function phoneFormat(
string exitCode = "",
string countryCode = "",
string areaCode = "",
required string telephonePrefix,
required string lineNumber
) {
var result = "#telephonePrefix#-#lineNumber#";
if ( areaCode.len() ) {
result = "#areaCode#-#result#";
}
if ( countryCode.len() ) {
result = "#countryCode#-#result#";
if ( ! exitCode.len() ) {
result = "+#result#";
}
}
if ( exitCode.len() ) {
result = "#exitCode#-#result#";
}
return result;
}
</cfscript>
As you can see, the first three arguments in the function signature are optional. Only the last two arguments are required. And the list of arguments, in totality, reads well — it reads naturally. And, when we run this ColdFusion code, we get the following output:
555-6767
+1-212-555-6767
011-39-99-555-6767
Again, this is not supposed to be a demonstration of how to format a phone number - this was just an example of where such a strategy would make sense. Other contexts might be formatting shipping addresses:
- Name. (required)
- Care Of.
- Street 1. (required)
- Street 2.
- City. (required)
- State. (required)
- Zipcode. (required)
- Country.
Or names:
- Prefix.
- First Name.
- Middle Name.
- Last Name. (required)
- Suffix.
- Professional Title.
In the vast majority of cases, I do think it makes sense to have the required arguments listed first within the method signature. And, in the vast majority of cases, I think this also makes semantic sense. But, when the semantics don't align well with the minimum requirements, ColdFusion gives us options — it doesn't lock us into the same confines seen in many other languages. Using these options is working with the grain of the language, not against it.
Want to use code from this post? Check out the license.
Reader Comments
I have been looking into this performance wise,
using typed / required / defaults arguments add overhead.
https://luceeserver.atlassian.net/browse/LDEV-5933
What are you trying to achieve here?
if it's documentation, simply use a proper doc block
https://docs.lucee.org/reference/annotations.html
As specifying a type in cfml simply means, check if this arg can be cast to a string/numeric/boolean/whatever, the argument remains whatever was passed
Of course with member functions, of course the type can matter, but they are slower with runtime lookup, compared to a good old BIF, which casts the variable to a string, if a string is needed
Required and defaults? As unspecified args end up null, consider letting the function simply fast fail, or use an elvis
@Zac,
It's just for the mental model of it all. Looking at your Jira ticket, I don't think any of my BIFs/methods would fall under the optimized path, even if I tried to remove a lot of annotation. Seems that you have to have pass-by-reference types only; but, 99% of my methods, I assume, include simple pass-by-value references (strings, numbers, booleans, etc). I'd be curious to know how many methods actually make it under this barrier to entry. I could imagine maybe methods that process API responses (structs) or process errors (struct-like).
Would it make more sense to have this as a setting like CFC-type checking? So that you could have slow stuff in development and faster stuff in production?
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →