Tuesday 22 April 2014

ColdFusion 11: quick look at what data-type built-in functions are, in the context of using them as first-class functions

G'day:
That was a foolishly long title. Oh well. The article itself is gonna be a quicky as I haven't had dinner yet and I'm getting hungry. Once I've cooked and got wine in hand... I'm not gonna be interest in writing this up.

Awdhesh and Rakshith gave an online presentation today on some of the language features of ColdFusion 11, and the subject of built-in functions being elevated to first class status came up. I thought I had covered this, but I'm buggered if I can find the article. If this is a double-up: sorry.

Firstly: what's a first-class function? Wikipedia explains it succinctly ("First-class function"):

In computer science, a programming language is said to have first-class functions if it treats functions as first-class citizens. Specifically, this means the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures.
Make sense? Basically you know how you can do this with bespoke functions:

function doStuff(){
    // stuff here
}

result = doStuffWithAHandler(dataToProcess, doStuff){
    // use doStuff() to assist processing dataToProcess
}

Or even inline with a function expression:

result = doStuffWithAHandler(dataToProcess, function doStuff(){
    // stuff here
}){
    // use doStuff() to assist processing dataToProcess
}

Until ColdFusion 11, one could pass one's own functions around like that, but one could not pass CFML functions around like that, eg:

function decorateText(text, decorator){
    return decorator(text);
}

message = "G'day world!";
shoutyMessage = decorateText(message, ucase);

writeDump([message, shoutyMessage]);

Yielding:

array
1G'day world!
2G'DAY WORLD!

On ColdFusion 10 and earlier, one would get this instead:


Variable UCASE is undefined.

So, anyway, there you go: ColdFusion's built-in functions are now first-class.

-ish.

Ray wondered out loud during the presentation what datatype a built-in function is, and I had tested this but not finalised the code. So I've done-so this evening. here we go, here's a bit of discovery. In the code below I basically run two tests on three things.

The tests are:


  • what datatype things are;
  • how those datatypes stack-up when used as a function.

And the three things I test are:
  • a bespoke function defined by function statement
  • a bespoke function defined by function expression
  • the built-in CFML function mid()

I've wrapped some calls in a safe() function so that I don't need to try/catch everything all over the place.

The red text is where I encountered problems, I'll discuss below.


writeOutput("<h2>Data types</h2>");
writeOutput("<h3>Function declared by statement</h3>");
function declaredViaStatement(){}
writeDump(var={object=declaredViaStatement, type=declaredViaStatement.getClass().getName()});

writeOutput("<h3>Function declared by expression</h3>");
declaredViaExpression=function(){};
writeDump(var={object=declaredViaExpression, type=declaredViaExpression.getClass().getName()});

writeOutput("<h3>Built-in function</h3>");
writeOutput("<h4>Via direct reference</h4>");
safe(function(){
    writeDump(var={object=mid, type=mid.getClass().getName()});
});

writeOutput("<h4>Via indirect reference</h4>");
safe(function(){
    var midRef = mid;
    writeDump(var={object=midref, type=midref.getClass().getName()});
});

writeOutput("<h4>Via indirect reference outside of closure</h4>");
midRef = mid;
safe(function(){
    writeDump(var={object=midref, type=midref.getClass().getName()});
});


writeOutput("<h2>return types</h2>");

function function regurgitateFunction(required function f){
    return f;
}

writeOutput("<h3>Function declared by statement</h3>");
safe(function(){
    resultWithDeclaredViaStatement = regurgitateFunction(declaredViaStatement);
    writeDump(var={object=resultWithDeclaredViaStatement, type=resultWithDeclaredViaStatement.getClass().getName()});
});

writeOutput("<h3>Function declared by expression</h3>");
safe(function(){
    resultWithDeclaredViaExpression = regurgitateFunction(declaredViaExpression);
    writeDump(var={object=resultWithDeclaredViaExpression, type=resultWithDeclaredViaExpression.getClass().getName()});
});


writeOutput("<h3>Built-in function</h3>");
midref = mid;
safe(function(){
    resultWithBuiltInFunction = regurgitateFunction(midref);
    writeDump(var={object=resultWithBuiltInFunction, type=resultWithBuiltInFunction.getClass().getName()});
});


function safe(f){
    try {
        f();
    }catch(any e){
        writeOutput("#e.type#: #e.message#; #e.detail#<br>");
    }
}

The output here is as follows:


Data types


Function declared by statement

struct
OBJECT
function declaredViaStatement
Arguments:none
ReturnType:Any
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
TYPEcftype2ecfm379207746$funcDECLAREDVIASTATEMENT

Function declared by expression

struct
OBJECT
closure _CF_ANONYMOUSCLOSURE_0
Arguments:none
ReturnType:Any
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
TYPEcftype2ecfm379207746$func_CF_ANONYMOUSCLOSURE_0

Built-in function


Via direct reference

Expression: Variable MID is undefined.; 

Via indirect reference

Expression: Variable MID is undefined.; 

Via indirect reference outside of closure

struct
OBJECT
object of coldfusion.runtime.CFPageMethod
Class Namecoldfusion.runtime.CFPageMethod
Methods
MethodReturn Type
getInstance(java.lang.String)coldfusion.runtime.CFPageMethod
getName()java.lang.String
invoke(java.lang.Object, java.lang.String, java.lang.Object, java.lang.Object[])java.lang.Object
TYPEcoldfusion.runtime.CFPageMethod

return types


Function declared by statement

struct
OBJECT
function declaredViaStatement
Arguments:none
ReturnType:Any
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
TYPEcftype2ecfm379207746$funcDECLAREDVIASTATEMENT

Function declared by expression

struct
OBJECT
closure _CF_ANONYMOUSCLOSURE_0
Arguments:none
ReturnType:Any
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
TYPEcftype2ecfm379207746$func_CF_ANONYMOUSCLOSURE_0

Built-in function

function: The value returned from the regurgitateFunction function is not of type function.; If the component name is specified as a return type, it is possible that either a definition file for the component cannot be found or is not accessible.

So, in summary:
Function declared via statementcftype2ecfm379207746$funcDECLAREDVIASTATEMENT
Function declared via expressioncftype2ecfm379207746$func_CF_ANONYMOUSCLOSURE_0
Built-in functioncoldfusion.runtime.CFPageMethod

Not very user-friendly types there, but they are - I guess - artifacts of the ColdFusion approach to compilation, rather than a type from the point of view of what CFML thinks is a type (eg: "function").

We'd seen before that passing a built-in function as a callback works OK. But there are some other issues which are a bit bung.

Firstly there's this code:

writeOutput("<h4>Via direct reference</h4>");
safe(function(){
    writeDump(var={object=mid, type=midref.getClass().getName()});
});

writeOutput("<h4>Via indirect reference</h4>");
safe(function(){
    var midRef = mid;
    writeDump(var={object=midref, type=midref.getClass().getName()});
});

writeOutput("<h4>Via indirect reference outside of closure</h4>");
midRef = mid;
safe(function(){
    writeDump(var={object=midref, type=midref.getClass().getName()});
});

The first two approaches to using mid() as a first class function here failed:


Via direct reference

Expression: Variable MID is undefined.;

Via indirect reference

Expression: Variable MID is undefined.;


So I cannot reference mid() directly in this context - in the context of within some closure code, it seems - it's not until I create the reference outside the closure code that the reference works.

So that's weird.

And then at the bottom, I have a function which both takes and returns an object of type function:


function function regurgitateFunction(required function f){
    return f;
}

And it seems I can pass mid() into this function - so when it goes into the function it qualifies as a function - but by the time it gets passed out: not a function any more. Weird.

function: The value returned from the regurgitateFunction function is not of type function.; If the component name is specified as a return type, it is possible that either a definition file for the component cannot be found or is not accessible.

That doesn't make a whole heap of sense to me.

To be clear... anywhere a function should be allowed in my CFML: a built-in function should "pass" type checking.

So I think there's a coupla bugs to fix here. I'll raise 'em and update this once I've got my dinner sorted out. Bugs: 37493013749296.

Righto.

--
Adam