Adding Differ() And DifferNoCase() Built-In Function Extensions In Lucee CFML 5.3.7.47
Yesterday, I needed to check to see if two Strings were different based solely on character-casing. To do this, I used ColdFusion's native compare()
function which performs a case-sensitive comparison between two values. However, the compare()
function is "funny" in that it returns 0
- a Falsy value - if the two strings are the same. Meaning, they are the same if the expression !compare()
returns true
. My brain is not good at reading "not expressions" - too many negatives for my mental call-stack. As such, it made me wish there was an inverted case-sensitive comparison operation in ColdFusion. In Lucee CFML, we can actually install user-defined function (UDF) extensions right into the runtime. I thought it might be fun to revisit that idea for this scenario in Lucee CFML 5.3.7.47.
For yesterday's task, I ended up with code that looked like this:
var isChanged = !! compare( valueA, valueB );
Since the compare()
returns a 0
when the values are the same, I had to use the "double not" operator (!!
) in order to coerce the non-zero numeric value into a Boolean when translating this into a "difference". Ideally, I'd like to have a function that does this for me. Something like:
var isChanged = differ( valueA, valueB );
I don't love the name, differ
; but, I couldn't think of anything that I liked more. So, I'm going to build two functions to use as extensions:
differ( a, b )
- Returnstrue
if the values are different using a case sensitive comparison.differNoCase( a, b )
- Returnstrue
if the values are different using a case insensitive comparison.
Under the hood, these are just going to use the compare()
and compareNoCase()
built-in functions, respectively.
When building Lucee CFML extensions, each custom function must be in it's own file. If you attempt to put more than one function in the same file, nothing will error - the second Function simply won't register as an extension. As such, I created two files:
<cfscript>
/**
* I determine if the two values are different use a CASE SENSITIVE operation.
*
* @valueA I am the first value to compare.
* @valueB I am the second value to compare.
*/
public boolean function differ(
required string valueA,
required string valueB
) {
// The compare() method performs a CASE SENSITIVE operation and returns 0 if the
// values are the SAME; and, a non-zero result if the two values are different.
var isSameValue = ( compare( valueA, valueB ) == 0 );
return( ! isSameValue );
}
</cfscript>
And, for a case insensitive comparison:
<cfscript>
/**
* I determine if the two values are different use a CASE INSENSITIVE operation.
*
* @valueA I am the first value to compare.
* @valueB I am the second value to compare.
*/
public boolean function differNoCase(
required string valueA,
required string valueB
) {
// The compareNoCase() method performs a CASE INSENSITIVE operation and returns 0
// if the values are the SAME; and, a non-zero result if the two values are different.
var isSameValue = ( compareNoCase( valueA, valueB ) == 0 );
return( ! isSameValue );
}
</cfscript>
As you can see, there is nothing very special happening here - all I'm doing is taking the native compare()
and compareNoCase()
functions and I'm proxying them with a NOT operator.
My only goal here to make my final code like 2% more readable by using a function name that lends a little bit better to what it's actually doing.
Once I had these files, I had to compile them - along with a MANIFEST.md
file - down into a .lex
archive. I can do this with a single compress()
call:
compress( "zip", "./ext/", "./differ-extension.lex", false );
Then, I uploaded the differ-extension.lex
file into the Lucee CFML Server Admin and restarted the server. At that point, my two User-Defined Functions (UDFs) are globally available in my Lucee CFML runtime! Let's try it out:
<cfscript>
// Testing prior to deploying the .lex file.
// --
// include "./ext/functions/differ.cfm";
// include "./ext/functions/differNoCase.cfm";
echoMany( "differ( a, A ) →", differ( "a", "A" ) );
echoMany( "differ( A, A ) →", differ( "A", "A" ) );
echoMany( "differ( A, Z ) →", differ( "A", "Z" ) );
echoMany();
echoMany( "differNoCase( a, A ) →", differNoCase( "a", "A" ) );
echoMany( "differNoCase( A, A ) →", differNoCase( "A", "A" ) );
echoMany( "differNoCase( A, Z ) →", differNoCase( "A", "Z" ) );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I collapse the variadic arguments down into a space-delimited list and echo them to
* the output. A <BR> tag is added automatically.
*
* @1...N Simple values to output.
*/
public void function echoMany() {
echo( arrayToList( arguments, " " ) & "<br />" );
}
</cfscript>
And, when we run this ColdFusion code, we get the following output:
Works like a charm!
I'm still a little bit on the fence about how I actually feel about installing custom functions as runtime extensions. On the one hand, it's awesome. But, on the other hand, I feel like it could easily confuse developers who are seeing what looks like a native function that doesn't appear to be documented anywhere on the Lucee CFML site. Still, a fun experiment for a Friday morning.
Want to use code from this post? Check out the license.
Reader Comments
I agree about your concerns about slightly invisible BIFs
The extension detail page in the admin should show which tags or functions the extension implements. A summary page of what functions are available via installed extensions would also be good.
Could be a nice PR for Lucee
@Zachary,
One thing I thought of was possibly prefixing custom functions with a key to denote ownership. I was inspired by what Angular suggests with their directives - including an element prefix - such as
inv-
to signify "InVision" - for custom elements. The same could be done with custom global UDFs. So, I might rename the functions to:invDiffer()
invDifferNoCase()
Then, a newer developer looking at this could maybe get a sense that the
inv
prefix would signify that something custom is taking place; and, would be able to search - or ask another developer for help - more effectively.For something non-enterprisey related, maybe a simple
udf
prefix would be better. So, again to make it concrete, maybe I could rename them to:udfDiffer()
udfDifferNoCase()
... just thinking out-loud here.