Wednesday 3 September 2014

Wrong wrong wrong, Cameron is wrong

G'day:
Adam Tuttle and I were talking on IRC about some of my code today - in the context of closure - and I brashly asserted the code might implement closure, but it didn't actually use it, so it was a bad example of closure in action (like, admittedly, almost all examples people use when demonstrating it).  I further posited I could simply use declared functions instead of inline function expressions and the code would still work, thus demonstrating my case.

TL;DR: I was wrong about that. But it doesn't sit entirely well with me, so here's the code.

First the original version under discussion:

// reduce.cfm
letters = [
    ["a", "b", "c", "d", "e"],
    ["b", "c", "d", "e"],
    ["a", "b", "c"]
];

result = letters.reduce(function(reduction, current, index){
    if (index==1) return reduction;
    return current.filter(function(element){
        return reduction.contains(element);
    });
}, letters[1]);
writeDump(result);

All this does is take an array of arrays an reduce it down to one array which contains only the elements present in all of the original arrays. So in this case: ["b", "c"].

Adam's contention was that the call to contains on reduction is using closure. My contention was that it doesn't, and I could rewrite that to not use inline function expressions, which would remove closure from the equation, and it would still work. Here's my code that didn't work:

// reduceWithoutClosure.cfm
letters = [
    ["a", "b", "c", "d", "e"],
    ["b", "c", "d", "e"],
    ["a", "b", "c"]
];

function filterHandler(element){
    return reduction.contains(element);
}

function reductionHandler(reduction, current, index){
    if (index==1) return reduction;
    return current.filter(filterHandler);
}

result = letters.reduce(reductionHandler, letters[1]);
writeDump(result);

My contention is that when filterHandler() references reduction, then it would see the reduction variable in reductionHandler() just fine, because functions can always "see" their parent's context. And the difference between closure and non-closure is simply that the binding to the parent context when closure is being used is done at declaration time, whereas with a "normal" function, it's done at execution time. And given when filterHandler() is called, its parent context knows what reduction is, it'd work fine.

Err: no. It seems that whilst a function can see the variables in the outermost calling code in the file, it doesn't intrinsically see the intermediary parent function's variables.

Initially I thought CFML was just getting it wrong (although my confidence had dropped from 95% to 50% now), so rejigged the code to run on JavaScript:

// reduceWithoutClosure.js
letters = [
    ["a", "b", "c", "d", "e"],
    ["b", "c", "d", "e"],
    ["a", "b", "c"]
];


function filterHandler(element){
    return reduction.indexof(element) >= 0;
};

function reductionHandler(reduction, current, index){
    if (index==0) return reduction;
    return current.filter(filterHandler);
}

result = letters.reduce(reductionHandler, letters[0]);

And that was the same. BTW: quite please how little I needed to change between CFML and JS in this case!

But, anyway, I got the wrong end of the stick there, and let Adam know (what he already knew) straight away.

Note, we can cheat here and do this:

function reductionHandler(reduction, current, index){
    variables.reduction = reduction;
    if (index==1) return reduction;
    return current.filter(filterHandler);
}

Which demonstrates using the variable in the parent's context, but that's not what I was expecting to be the case.

Oh well: it's good to learn stuff. And, hey, I liked my original solution to his array reduction problem anyhow!

Cheers Adam

--
The Other Adam