Though IsDefined()
has been a frequent topic recently, I’m fairly certain the following questions and issues deserve their own separate and definitive topic. I have spent literally DAYS researching and obsessing over every little detail and now away we go!
-
Why was
IsDefined()
deprecated? If performance was the only reason, then where should we draw the line between performance vs natural language readability and fewer characters to type for rapid app development? Even with autocomplete, “isD” is fewer characters than “StructK”. Taking the performance argument to the extreme, should Lucee itself be deprecated and replaced with Assembly Language?
- As Mark Kruger wrote way back in 2009, “This function is commonly used in most ColdFusion applications. In fact, I would put it in the top 10 of functions used (perhaps the top 5)”. Judging from the frequency of surprised responses within this forum about it being deprecated, its popularity probably hasn’t declined much. It is also apparently not deprecated in Adobe CF, which will make cross-platform compatibility more difficult should
IsDefined()
ever be completely removed from Lucee in the future. That doesn’t impact me personally since I migrated from Adobe CF to Lucee about 5 years ago and never looked back, but it’s something to consider when encouraging Adobe CF managers and devs to make the switch.
- Other than
StructKeyExists()
, the other alternativesParameterExists()
,!isNull()
, and the Elvis null-coalescing operator all appear to be even less performant thanIsDefined()
. If performance is the sole criteria, then why haven’t those also been deprecated? Note:ParameterExists()
is deprecated in Adobe CF. Maybe the Lucee doc page just need to be updated?
-
What if traversing all the scopes is precisely the functionality that’s needed for a specific case? And since
IsDefined()
has the best performance in that case, do we now have a reason forIsDefined()
to be … umm … undeprecated? Or is the act of deprecating it a means of encouraging developers to learn about the performance impact? If so, that doc page needs that explanation and recommendations for replacements, and also … return to #3.
-
Is it feasible to change the Java algorithm for
IsDefined()
to limit the scope when specified instead of traversing all scopes so its performance is on par withStructKeyExists()
? And while we’re at it, can we remove the requirement for the argument to be a string so it’s more likeParameterExists()
andIsNull()
? Seriously though, that would be less keystrokes not having to type the double quotes.
-
OR … improve the performance of
IsNull()
? Semantically, though, I preferIsDefined()
because a var can be defined asNullValue()
and though that is treated as if it doesn’t exist, in fact the key still exists within its defined scope (see my gist below). OTOH,IsDefined()
isn’t 100% semantically correct either, when the var is defined asNullValue()
. I know that’s because of how “truthiness” works in Lucee (and Java?), but nevertheless … well now it looks like I’m arguing forIsDefined()
andIsNull()
to both be deprecated because they’re not 100% semantically correct. HAHA brain fog!
IsNull() Not So Different Than IsDefined()
Ben Nadel: “From what I have seen and heard before, I believe that IsNull() gets converted to StructKeyExists() at compile time;”
Safe Navigation Operator Also?
Adobe CF Language Enhancements: “Generally, a referenced variable is searched in various scopes before a match is found. In cases where a variable is not found in common scopes like function, local scope, and variable scopes, the search continues in implicit scopes, which can take a while.”
Is that different in Lucee? Because in my benchmarks SNO is 2nd fastest after StructKeyExists()
.
BENCHMARKS
First it’s important to verify that the compared function and operator results are equivalent, so let’s start with this:
WhatIsTruth('Url.foo not declared');
Url.foo;
WhatIsTruth('Url.foo;');
Url.foo = NullValue();
WhatIsTruth('Url.foo = NullValue();');
Url.foo = "hey";
WhatIsTruth('Url.foo = "hey";');
function WhatIsTruth(header) {
echo('<h1>#header#</h1>');
dump(label='Url', var=Url)
dump(label='IsDefined("Url.foo")', var=IsDefined("Url.foo"));
dump(label='ParameterExists(Url.foo)', var=ParameterExists(Url.foo));
dump(label='StructKeyExists(Url, "foo")', var=StructKeyExists(Url, "foo"));
dump(label='!isNull(Url.foo)', var=!isNull(Url.foo));
dump(label='(Url.foo ?: false) != false)', var=(Url.foo ?: false) != false);
dump(label='(Url.foo ?: false) !== false)', var=(Url.foo ?: false) !== false);
dump(label='Url?.foo != NullValue()', var=Url?.foo != NullValue());
dump(label='Url?.foo !== NullValue()', var=Url?.foo !== NullValue());
echo("<br>");
}
The only difference is !=
vs !==
for safe navigation, though I like that differentiation for the case of Url.foo;
because an empty string is not equal to NullValue()
.
The timers I forked from @Zackster and bumped the iterations up to a million each. Though this isn’t a real world benchmark, it’s probably enough evidence since StructKeyExists()
consistently shows the best performance … except when I run it on my laptop where (surprisingly?) the safe navigation operator is comparable to or even faster!
echo('IsDefined("Url.customer")');
timer type="outline" {
loop times=1000000{
IsDefined("Url.customer");
}
}
echo('StructKeyExists(Url, "customer")');
timer type="outline" {
loop times=1000000{
StructKeyExists(Url, "customer");
}
}
echo('ParameterExists(Url.customer)');
timer type="outline" {
loop times=1000000{
ParameterExists(Url.customer);
}
}
echo('!IsNull(url.customer)');
timer type="outline" {
loop times=1000000{
!isNull(url.customer);
}
}
echo('Url.customer ?: false');
timer type="outline" {
loop times=1000000{
Url.customer ?: false;
}
}
echo('(Url.customer ?: false) != false');
timer type="outline" {
loop times=1000000{
(Url.customer ?: false) != false;
}
}
echo('(Url.customer ?: false) !== false');
timer type="outline" {
loop times=1000000{
(Url.customer ?: false) !== false;
}
}
echo('Url?.customer !== NullValue()');
timer type="outline" {
loop times=1000000{
Url?.customer !== NullValue();
}
}