Reflecting On Natural Language Operators In ColdFusion
The other day, on the Lucee Dev Forum, I suggested that ColdFusion might benefit from having starts with
and ends with
operators. These would fall under the "natural language" operators, in that they read like normal human language, not computer jargon. But, my suggestion is somewhat fraudulent considering the fact that I never use the natural language operators in ColdFusion. This conversation, however, gave me pause to reflect on this choice more deeply.
In ColdFusion, there's nothing "unique" about the natural language operators. Everything that can be done with human speak can also be done with more traditional operators and function calls. For example, here's two sets of expressions that all evaluate to Truthy outcomes. The first half uses the natural language operators and the second half uses the standard equivalents:
<cfscript>
// All of these tests have a "Truthy" outcome.
// Natural language operators.
echo( "foo" is "foo" );
echo( "foo" equal "foo" );
echo( "foo" contains "oo" );
echo( "foo" does not contain "bar" );
echo( "foo" not equal "fooo" );
echo( 2 greater than 1 );
echo( 1 less than or equal to 2 );
// Computer science operators.
echo( "foo" == "foo" );
echo( "foo".findNoCase( "oo" ) );
echo( ! "foo".findNoCase( "bar" ) );
echo( "foo" != "fooo" );
echo( 2 > 1 );
echo( 1 <= 2 );
</cfscript>
Note: String-based operators are all case-insensitive in ColdFusion. Which is why I am using the
.findNoCase()
function and not the.find()
function.
As you can see, the natural language operators are a bit more friendly. And, can more easily be understood by those less familiar with the language. But, they are much wordier. And, they don't offer anything unique. That is, there's nothing that they can do that can't be done using other language semantics.
For example, my suggestion to add starts with
and ends with
operators can be accomplished either with existing constructs or with custom functions (UDF). The following code breaks because these operators don't actually exist in CFML. But, this shows my intent; and, demonstrates that such made up operators can already by implemented using either Regular Expression anchors or string slicing:
<cfscript>
// CAUTION: These DO NOT EXIST in the CFML language.
// CAUTION: These DO NOT EXIST in the CFML language.
// --
echo( "ben@bennadel.com" starts with "ben" );
echo( "ben@bennadel.com" does not start with "sarah" );
echo( "ben@bennadel.com" ends with "@bennadel.com" );
echo( "ben@bennadel.com" does not end with "@example.com" );
// --
// CAUTION: These DO NOT EXIST in the CFML language.
// CAUTION: These DO NOT EXIST in the CFML language.
// Using existing RegEx constructs (with boundary anchors).
echo( "ben@bennadel.com".reFindNoCase( "^ben" ) );
echo( ! "ben@bennadel.com".reFindNoCase( "^sarah" ) );
echo( "ben@bennadel.com".reFindNoCase( "@bennadel\.com$" ) );
echo( ! "ben@bennadel.com".reFindNoCase( "sarah$" ) );
// Using user defined functions (UDF).
echo( startsWith( "ben@bennadel.com", "ben" ) );
echo( ! startsWith( "ben@bennadel.com", "sarah" ) );
echo( endsWith( "ben@bennadel.com", "@bennadel.com" ) );
echo( ! endsWith( "ben@bennadel.com", "@example.com" ) );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
public boolean function startsWith(
required string source,
required string substring
) {
return( left( source, len( substring ) ) == substring );
}
public boolean function endsWith(
required string source,
required string substring
) {
return( right( source, len( substring ) ) == substring );
}
</cfscript>
There is something very alluring about the natural language operators. So, why don't I ever use any of them?
My first reaction is extremely superficial: some part of me believes that "real programmers" don't use these types of operators. What is this based on? ABSOLUTELY NOTHING! This is just some weird-ass bias that my brain has developed.
I wonder if it is rooted in the old tag-based constraints in the early days of ColdFusion. It used to be that in tag-based markup, you couldn't use all of the available operators. For example, if you tried to use "greater than" in a <cfif>
tag:
<cfif ( query.recordCount > 3 )>
... ColdFusion would blow up because it thought the greater than (>
) operator was actually the end of the <cfif
tag. As such, when dealing with tags, you had to replace the bracket-based operators with things like gt
, gte
, lt
, and lte
(I think you even had to replace ==
with eq
if memory serves):
<cfif ( query.recordCount gt 3 )>
I think this original constraint created an accidental hierarchy in my head: there were the operators that I wanted to use; and then, there were the operators that I had to use due to limitations of the CFML parser. And, while this really only applied to some of the operators, this divergence automatically grew to include all of the natural language operators. Meaning, the natural language operators were the operators that were forced on me due to the compiler.
This made me view the natural language operators in a diminishing light. They became the "runner up" operators. The "ugg, I can't believe this compiler is still broken, why won't they fix it" operators.
This isn't fair to the natural language operators. And, of course, the logic and reasoning parts of my brain do not diminish the operators or the developers who use them. But, I'm only human; and, I will admit that I do get a small twinge when I see them in code.
But, again, that's my own baggage.
Another reason that I push back against them is that some of them are just a lot more to read. The most egregious one is, less than or equal to
. Compare that with, <=
. Not only is <=
easier to read, it's a single token. Which makes it much easier for the brain to recognize and process in its pattern matching.
And, here's some more of my mental baggage: I crave consistency. Which means, some part of me avoids the natural language operators simply because it would break my pattern of usage. This is so silly, I know! But, I have trouble accepting the idea that some of my expressions can use the terse operators and some of my expressions can use natural language operators. Some part of me feels that mixing-and-matching is "sloppy". Like when someone pastes a Script-based Function into a Tag-based Component without rewriting it—sloppy!
But, again, this is just my own emotional baggage!
Another reason that I have trouble with natural language operators has nothing to do with ColdFusion at all: the unless
operator. I believe this is a Ruby operator that allows you to write code like this:
return "yes" unless psyched_you_out
I suppose this isn't really an "operator", it's more of a control-flow construct (like if
and else
). But—to me—this way of writing code is just bat-shit crazy! It feels like an attempt to use "natural language" to improve things; but, all it does it make the code more confusing by putting the condition after the action being taken.
I don't quite know why seeing unless
scarred me so deeply. I don't even use Ruby; and yet, this construct haunts me. And, I am sure, it has unfairly tainted a larger "natural language" approach.
In the end, I don't think I have any compelling reason to entirely rule-out natural language operators. The biggest thing holding me back is my own emotional baggage. And, I'm sure that the only way I can overcome such a personal hurdle is with usage. So, maybe I'll force myself to use more of the natural language operators and see how it feels.
Want to use code from this post? Check out the license.
Reader Comments
I fully relate 👏
@Chris,
At least I'm not the only crazy one 😛
Great, now ChatGPT is going to parse your blog and it won't be long before GitHub CoPilot is suggesting your fake operators to everyone! 😁
I had the same mental association with
GTE
,LT
, etc as the "second-class" operators I had to use in tags back in the day due to parsing issues. I don't tend to use theGREATER THAN EQUAL TO
sort of ones just because I think>=
is also quite readable and much more concise. I do usecontains
quite often however as I do like the readability and it saves me one more "nesting" of parenthesis around my value.I do use
someString.startsWith()
fairly often, but one must be VERY CAREFUL as that is a member function on thejava.lang.String
class and is case sensitive! There's a fighting chance I might use something likein my code, but I do also half expect the rest of the programming world to laugh at that as "not a real language".
@Brad,
I can relate. The
contains
feels like the only one of the existing ones that I would really consider using. But, I have such a mental block to it. And, I'm always so torn about wanting them to add more member-methods to the data types. Does it just start to bloat the language? Would adding"string".startsWith()
to the CFML spec itself be a problem? Or should that really be in a utility library?Obviously, as you point out, Java answered that question by adding directly to the
java.lang.String
class. But, it can be a slipper slope. I never know what the right answer is.I wish I had a litmus test where I could easily answer the question: should it be a member method, as in:
String.includes( value )
... or, should it be in a standard utility library like:
StringUtils.includes( source, value )
I wish I had some decision tree that made it easy to answer that type of a question.
Sadly yes, because a lot of code and even frameworks today rely on that calling the Java method which is case sensitive, so adding a CFML version would be a breaking change. This is one of the issues with having a sort of mashup between CF member functions and actual Java member functions. The former employ a fair bit of magic and used to not work if the simple value was actually a boolean or float under the covers. The latter defo requires you to have an actual java string, but at least that is understandable.
It's interesting to note Java also has a handful of helper methods for things like files or arrays in their
Files
andArrays
class In some cases, Java will put helper methods directly on interfaces such asMap.of()
. I don't know what Java's litmus test is-- most of these cases involve factory methods for creating new instances, but Java'sArrays
class has an boatload of methods for things like filling an array with values, or sorting an array.https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html
Though, considering how arrays are a primitive in Java and have no methods, perhaps that's the real reason why these methods live here.
@Brad,
Yeah, that's a great point re: case-sensitive Java call vs. what would presumably be a case-insensitive ColdFusion call. Would absolutely break some stuff that is already out in the wild. 😨 There's definitely no easy answer for that.
Unless they made it into an Application.cfc setting — just kidding 🤣
Letters tend to be easier for me to type without much thought as opposed to symbols. Yes, they're just one row further away, but I am much more accustomed to typing letters than brackets and equal signs, so it requires less "work" to just type
gte
compared to>=
.I still generally prefer symbols, but when I have the pleasure of working with ColdFusion for a change, it's nice to be able to just go for the more comfortable to type operators.
@Kevin,
I actually find the
eq
,gt
,gte
,lt
, andlte
operators kind of nice. Since our code-base is so old, we still have a good number of these mixed in with the view-templates; and, my brain seems to process them just as easily as anything else. They are nice and short.Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →