Friday 14 December 2012

Am I right to think this is stupid?

G'day:
This was brought to my attention via a thread on the Adobe forums in which someone was messing around with "closure" functions being used as callbacks.  One of the statements they made was:

The only thing I observed: a closure does not seem to be a function, because isCustomFuntion (..) returns false. Maybe this should be changed, I dunno.
My reaction to that was "[cough]bullshit", but I decided I better not back my instinct, I'd better make sure I was right first.  But I wasn't right. Typically, the online docs for isCustomFunction() are not much help here:

The IsCustomFunction function returns True for any function that can be called as a custom function, including functions defined using CFScript function declarations and cffunction tags, and functions that are ColdFusion component methods. For CFC methods, first instantiate the component.
On first take - other than having moderately curious wording -  that seems to suggest what is logical: if I create a function with function / cffunction etc, and pass it to isCustomFunction(), I'll get a TRUE result. If what I pass it is anything else (eg: just a string, or a struct or something), I'll get a FALSE.  However this is not the case.



My first instinct here was "bug". However on a whim I googled the docs some more, and found this page entitled "Modifications to the function isCustomFunction":

Though closure is a function object, it is not considered as a custom function.
The function now returns:

  • True: If name can be called as a custom function.
  • False: If name can be called as a closure.

Ignoring the pidgin English employed here, my reaction to this is... err... NSFW shall I say, so I'll leave that to your imagination and move on. I really do have to question why this is a separate page, and not just a footnote on the docs for the function it's talking about.  That seems like a cock-up too.

Anyway, I care less about the docs than I do about the wayward functionality here. Just to be really clear, I've employed my multimedia skills to present a Venn Diagram:



(I did not suggest my multimedia skills were any good. And the only tool I had available @ work was MSPaint ;-)

So we have a concept of functions. And we have two types of functions: functions that come with CF, and functions I write. The former are "built-in" functions, the latter are "custom functions".  Within "custom functions" we have two ways of implementing functions via a declaration:

<cffunction name="f">
</cffunction>

<cfscript>
    function g(){
        
    }
</cfscript>

And those via a function expression:

<cfscript>
    h = function(){
        
    }
</cfscript>

So there's two ways of creating a custom function. But they're all still functions, and they're all still custom functions.  The green bit in the Venn Diagram. In any way, shape or form one can use functions in ColdFusion, a function defined by declaration or by expression work equally well.  The chief proof here is that if one has a function - which is the chief place CFML is typeful - which takes either an argument of type function, or returns a type function: either will work. This is because they're both functions.

However ColdFusion (or the engineers @ Adobe, rather), don't seem to get this. Whether a function is created via a function declaration or a function expression is neither here nor there: they are still custom functions. You don't have any choice with that: that's what they are. So isCustomFunction() is wrong in its implementation there. If you want to have another function which is isCustomFunctionCreatedAsAnExpressionBecauseSomeHowWeConsiderThisToBeDifferentFromACustomFunctionCreatedByDeclaration(), then be my guest, but don't start ascribing meaning to "custom function" that isn't there.



Digression
Whilst writing this I have been reading and watching the news regarding the mass shooting in Connecticut. I hope no-one thinks it's trite if I mention this here, but I'd just like to echo everyone else's sentiments that this is absolutely dreadful. I dunno what else to say. But for some reason I am feeling rather guilty sitting here as I read about it, writing some dumb blog article and eating my pizza, so I figured I ought to say something.

[Shakes head, clears his throat & draws himself up. Continues typing...]


I will have to say, though, that looking into this and playing around with function declarations and expressions has taught me a bit today. I really didn't quite get the bind-time notion behind closure, but now I do. I think.  I had this code, which didn't seem to be use closure even though I was creating the function via an expression:

<cfscript>
    variables.when = "bound at declaration";
    
    f = function(){
        var bind = variables.when;
        writeOutput("#bind#<br />");
    };
    
    variables.when = "bound at execution";
    f();
    
    writeDump(
        {
            isCustomFunction = isCustomFunction(f),        
            isClosure = isClosure(f)
        }
    );
</cfscript>

This outputs:

bound at execution
struct
ISCLOSUREYES
ISCUSTOMFUNCTIONNO

I was going like "huh?" shouldn't that be saying "bound at declaration". No this was down to a misunderstanding on my part. The notion of binding - I understand now - is not synonymous with "copying" or "using the value as it stands right now", it just really means "contextualising". So if we focus on this statement, it's not that the current value of variables.when gets grabbed right then when f() is defined, it's that what gets decided at this point is that when f() gets called, the variables.when that it's referring to is the one in that context... in this case, the ones in my test.cfm file's variables scope.

I had my watershed moment when I came to run a different variation of this:

<cfscript>
    // test.cfm
    variables.when = "initial value in the calling-code's scope";
    
    f = function(){
        var bind = variables.when;
        writeOutput("#bind#<br />");
    };
    function g(){
        var bind = variables.when;
        writeOutput("#bind#<br />");
    }
</cfscript>
<cf_tag f="#f#" g="#g#" message="First Call">

<cfset variables.when = "updated value in the calling-code's scope">
<cf_tag f="#f#" g="#g#" message="First Call">

And this is tag.cfm, called as a custom tag above:

<cfscript>
    // tag.cfm
    param name="attributes.message" default="";
    
    param name="request.count" default=0;
    
    variables.when = "bound at execution #++request.count#";
    attributes.f();
    attributes.g();
    writeDump(
        var ={
            isCustomFunction = isCustomFunction(attributes.f),        
            isClosure = isClosure(attributes.f)
        },
        label=attributes.message
    );
</cfscript>

The output from this is as follows:

initial value in the calling-code's scope
bound at execution 1
First Call - struct
ISCLOSUREYES
ISCUSTOMFUNCTIONNO
updated value in the calling-code's scope
bound at execution 2
First Call - struct
ISCLOSUREYES
ISCUSTOMFUNCTIONNO

So that's where I got it: the "ah right!" moment. f(), defined as an expression in the calling code has its reference to variables.when bound to the one in test.cfm at the time the function expression was executed.  So when it's executed in a different memory context - that of the custom tag - it's still referencing the variables.when in the calling code. This is in contrast to the function g() defined via the function declaration, which binds its external variables when it's executed, not when it's declared.  Cool.

This, of course, just means this blogging exercise was useful for me. Adobe is still wrong about how isCustomFunction() works.  I've raised a ticket for it: 3429588.

And that's enough for this evening.

I'm about 75% of the way through a monster about regex, which I'll try to finish on the weekend. I think I'll need to cut it up into several posts though...

Righto.

--
Adam