Saturday, 30 January 2016

CFML (or probably LuceeLang) and what constitutes "The Truth"

G'day:
This all stems from a Jira ticket for Lucee: "Redefining truthy and falsey values (LDEV-449)". The way the ticket is worded means it's not convenient to copy and paste a chunk of its detail to explain the gist of it, so I'll reiterate.

At the moment CFML relies on type coercion rules determine which values are valid to be used where boolean values are expected. Obviously at the base of things boolean values are true or false. But because CFML is loosely and dynamically typed, one doesn't need to simply use actual booleans in boolean expressions. The following value for other types will also coerce to boolean when called for:

TypeValueCoerces to
String"true" (as opposed to true, which is a keyword, and fundamentally boolean already)true
String"false"false
String"Yes"true
String"No"false
Numeric!=0true
Numeric0false

There are other coercions that will feed into these, for example strings with numeric values - eg: "42" (as opposed to just 42) - will happily coerce into a numeric, then from there to a boolean.

What this ticket is about is extending this to have a notion of "truthy" and "falsy" for other values and other data types. This notion stems from Groovy (and perhaps other languages) which is pretty liberal with what it will consider truthy or falsy. These terms "truthy" and "falsy" denote the values are not actually boolean, but will pass / fail a boolean check.

Here's a quick demo, which is fairly self-explanatory:

print "populated string: "
println " " ? "truthy" : "falsy" 

print "empty string: "
println "" ? "truthy" : "falsy" 
println "============="

print "non-zero: "
println (-1 ? "truthy" : "falsy") 

print "zero: "
println 0 ? "truthy" : "falsy" 
println "============="

print "populated collection: "
println([null] ? "truthy" : "falsy") 

print "empty collection: "
println([] ? "truthy" : "falsy") 
println "============="

print "populated map: "
println([key:""] ? "truthy" : "falsy") 

print "empty map: "
println([:] ? "truthy" : "falsy")
println "============="

class Test{
    String value
    
    Test(value){
        this.value = value
    }
    
    boolean asBoolean(){
        return (value == "truthy")
    }
}

print "truthy object: "
println new Test("truthy") ? "truthy" : "falsy" 

print "falsy object: "
println new Test("anything else") ? "truthy" : "falsy" 
println "============="


And the output:

>groovy truthy.groovy
populated string: truthy
empty string: falsy
=============
non-zero: truthy
zero: falsy
=============
populated collection: truthy
empty collection: falsy
=============
populated map: truthy
empty map: falsy
=============
truthy object: truthy
falsy object: falsy
=============

>

So all the common data types, and indeed custom data types have a sense of truthiness (and, by inversion: falsiness).

This is the gist of the ticket. Basically... checking for emptiness in an object (irrespective of what the definition of "empty" is), is a very very common requirement, and this truthiness concept just gives the ability to abbreviate that via some synatactic sugar and an agreed-on method of identifying what it is to be empty, and fielding that as a boolean: empty is "falsy", and not-empty is "truthy".

I think it's a good idea.

There's been some kick-back on it by people who don't seem to understand the concept of "syntactic sugar" and simply "don't get" the ticket. I'll leave it to you to read their comments and form your own opinion.

Now, as for implementation, I think there is a critical step here, as suggested by that Groovy code. LAS need to implement this as an interface, for both Java and LuceeLang. See how the demo class I have there implements an asBoolean() method:

boolean asBoolean(){
    return (value == "truthy")
}

Any class that implements that method can then have its objects used in boolean expressions. Basically they need to implement something like a BooleanBehaviour interface. Obviously my class there doesn't implement any interfaces, but I have not got far enough into Groovy to know whether interface implementation is just fulfilled by behaviour, rather than by contract (like it is in CFML).

I think it's essential for Lucee's implemention of this to follow an approach like this. Not just to bung some behaviour in to allow its current types to magically behave like booleans, but implement it in a formal fashion. And also expose the same interface to both Lucee users' .lucee code, but Java code too.

The current ticket seems to be asking for this for Lucee's CFML implementation, but I think this is a mistake because some of the CFML's implementation's automatic coercion to boolean conflicts with the "truthy" approach. Most notably a string "0" in CFML is false, because it will be converted to a numeric, and 0 is false. But using sensible "truthy" behaviour for strings... "" is false, and any other string is true. It makes little sense (and violates POLA,  IMO) to have "" and "0" as false, and [any other string] be true. It would just make CFML look like it "didn't quite get it" when it implemented this feature.

Also note that Brad's narrative mentions toBoolean(), whereas Groovy's handling is asBoolean(). This might seem like a semantic difference, but I think it requires closer examination. Groovy has both toBoolean() and asBoolean(); and it looks like C# does too. Best LAS get this one right. At least understand the differences (which I don't, I have to say), before deciding how to approach.

Sorry this article isn't so interesting. I started to percolate away at it during the week and reckon I got enough content for an article, but never really achieved any real nuance to it. Still: for something that started as a Jira comment I guess it's all right.

Back to me Guinness...

--
Adam