Monday 25 May 2015

Lucee seems to mess up boolean expressions (kinda)

G'day:
Another bloody stumbling block writing this code (see CFML / Lucee: beware of "optional" semi-colons for the first one). This time Lucee's not getting the result of a OR expression right. Seriously. Well: I guess not as right as I'd like it to.



I was trying to write a compound sort callback, along these lines:

results = [
    {factor1=3,factor2=6},
    {factor1=3,factor2=5},
    {factor1=3,factor2=4},
    {factor1=2,factor2=3},
    {factor1=2,factor2=2},
    {factor1=1,factor2=1}
];

sortedResults = results.sort(function(e1,e2){
    return sgn(e1.factor1 - e2.factor1) || sgn(e1.factor2 - e2.factor2);
});

writeDump(sortedResults);

So the logic is here that factor1 is tested, and only if it's equal is factor2 checked. A fairly common requirement. On Lucee, this results in:


ie: no sorting taking place.

I had ColdFusion running too, so checked it on that:


That's more like it. So what the hell is going on with Lucee?

I put some debugging in, and groaned. Here's come code doing a truth table for the || operator:

operands = [-1,0,1];

operands.each(function(a){
    operands.each(function(b){
        writeOutput("#a# || #b# = #a || b#<br>");
    });
});

And the results:

-1 || -1 = true
-1 || 0 = true
-1 || 1 = true
0 || -1 = true
0 || 0 = false
0 || 1 = true
1 || -1 = true
1 || 0 = true
1 || 1 = true


Now. from a strictly boolean sense, where -1 and 1 are truthy, and 0 is falsy... this is correct.

However Lucee seems to be evaluating the expression as a whole, rather than short-circuiting. I expect -1 || -1 to be -1. It encounters the first truthy expression, and returns it. And -1 is truthy. There's no need to go any further.

But the issue is not that it's not short-circuiting per se (ie: it is short-circuiting, which is good), because if I drop factor2 from the last result, the sorting still works; so clearly the second half of the || expression is not being evaluated if it doesn't need to be. So it's like Lucee is just being a bit overly dogmatic for a loosely typed language: || expressions return booleans, so it's converting -1 into a true. Which... is not what it should be doing. Let's look at that truth table in ColdFusion:

-1 || -1 = -1
-1 || 0 = -1
-1 || 1 = -1
0 || -1 = -1
0 || 0 = 0
0 || 1 = 1
1 || -1 = 1
1 || 0 = 1
1 || 1 = 1


That's better. What about other loosely-typed languages though? Maybe my expectations are off. Here's a JS example:

operands = [-1,0,1];

operands.forEach(function(a){
    operands.forEach(function(b){
        console.log(a + " || " + b + " = " + (a || b));
    });
});

This outputs the same as CF does. And Python?

operands = [-1,0,1]

for a in operands:
    for b in operands:
        print("%d or %d = %d" % (a, b, a or b))

Again, the output was the same.

I tried Ruby, but it's a bit odd when it comes to boolean expressions. All numbers are true (even zero), so I cannot do a like-for-like example.

On the other hand, here's the Groovy equivalent:

operands = [-1,0,1];

operands.each {a->
    operands.each{b->
        println("${a} || ${b} = ${a || b}");
    };
};

And it agrees with Lucee:

-1 || -1 = true
-1 || 0 = true
-1 || 1 = true
0 || -1 = true
0 || 0 = false
0 || 1 = true
1 || -1 = true
1 || 0 = true
1 || 1 = true


Hmmm.

OK, so there's obviously not a cut and dried precedent here. That being the case, I'm going with my expectations of how CFML should do it being correct, and weight is also added to this given ColdFusion sets the standards for this sort of thing, and it behaves the way I'd expect.

I'm raising a bug with Lucee: LDEV-366.

--
Adam