Saturday 31 January 2015

CFML: the evolution of functionality

G'day:
Right, enough Lucee stuff for the time being. CFML. I still remember CFML.

As you all know, I am very unhappy about how Adobe and Railo had dealt with porting some residual tag-only functionality from tags to a script-sensible implementation.

I'm going to work through why they're both wrong and why I'm right in this article. Hey, I might as well lay my cards out, right?

<cfsavecontent>



I've actually floated my ideal implementation for savecontent in CFScript before: "How about this for savecontent?". This article is not a rehash of that, but describes how I got to my conclusion. This seems to be the thing that eludes the vendors' engineers / language designers.

Actually <cfsavecontent> is not a great example to use here, because I think it's really best just left as a tag. What it does doesn't make much sense in script, instead one should just use string concatenation. Some things are just better done in tags than script, I get that. We should all get that. I'm selecting it as an example as its implementation is easy to understand, and we're all familiar with it. The syntax considerations I'm discussing apply equally to all other similar situations.

So we have this in tags:

<cfsavecontent variable="capturedContent">
    <!--- string data output here --->
</cfsavecontent>

And this results in capturedContent containing "string data output here". Obviously the capturedContent will be everything that is emitted to "stdout" within the <cfsavecontent> tags, not just hardcoded strings.

I don't recall who jumped on this first with CFScript (I think CF9 had it before whatever Railo version had it), but we ended up with this from both vendors:

savecontent variable="capturedContent" {
    // string data output here
}

Seriously?

You utter gobshites, the pair of you.

Who the hell in their right minds invents a code construct which returns a value which does not implement the from:

result = somethingThatCreatesTheResult

What the hell were you thinking in deciding one passes the result variable name as a string to some weirdo construct like that? Who the hell signed off on that? They should have been sacked as being incompetent.

It gets worse. At least Railo and Adobe agreed on the syntax here. When implementing the rest of the functionality in script which had previously only been available in tags, Railo got there first with the same syntax as above (bad, but at least consistent):

<cfsometag attr="value">
    <!--- stuff goes in here --->
</cfsometag>

sometag attr="value" {
    // stuff goes in here
}

Adobe, in what is probably the worst architectural decision in a long history of pretty bloody ordinary architecture decisions went with this instead:

cfsometag(attr="value") {
    // stuff goes in here
}

(I just burying my head in my hands in despair at what those muppets are doing to our CFML language instead of typing).

Why this is utter crap:

  • as above, this "tags without curly brackets" approach is crap to start with;
  • it's not internally consistent with what had already been done (see: savecontent);
  • why the hell are you prefixing everything with cf? just... why?
  • other built-in functions in CFML don't take name-value pairs, they take ordered arguments;
  • but that's OK because despite appearances, that construct is not actually a function anyhow;
  • Railo beat you to this, and came up with a solution, but you decided to come up with your own, worse, incompatible solution.
Seriously, Adobe, how could you mess one thing up quite so much? If someone avoiding sacking the previous time around, they should have got their just deserts this time, surely?

Anyway, this is where we are. Three different ways of expressing tag-original functionality in script. With no cross-compat between languages.

But why was this such a hard exercise for them? Why did they make such a pig's ear of it?

It's because of the block requirement. Neither vendor could work out how to handle it properly.

What we basically have is this:

someResult = someFunctionality(someParameter) someBlockToProcessWithTheFunctionality

Where in this case the only non-obvious bit is someBlockToProcessWithTheFunctionality. This bit is the bit between the tags (in the tag implementation):

<!--- string data output here --->

The thing is, there's no mystery as to how to handle this. Other languages (or at least one) already deal with it:

someResult = someFunctionality do |someParameter|
    someBlockToProcessWithTheFunctionality
end

This is how Ruby deals with this sort of requirement. They have a notion of a block which can always be passed to a function call as a last argument. As to whether the function uses the block is another thing, but this is one of the baseline constructs in Ruby. Blocks are a new thing here, but the rest of it is predictable within the current syntax: someFunctionality takes someParameter, passes it to someBlockToProcessWithTheFunctionality and all that returns someResult. Obvious.

It's similar to the savecontent implementation, except it uses proper function syntax, and returns a result:


someResult = someFunctionality(someParameter){
    // someBlockToProcessWithTheFunctionality
};

That would be better. But it still has problems, as the makers of Ruby seem to have noticed:
  • Firstly it requires a new syntax construct: the function call which takes a curly-brace block as part of the call.
  • Secondly the block is not reusable. It's like an inline function expression in a way: can be used only once right there in place.
  • Thirdly (this is my opinion, not a Ruby thing), it's odd to have someParameter as an argument within the parentheses, but someBlockToProcessWithTheFunctionality - which is still basically a parameter to the functionality - is outside the parentheses. Why the special treatment for blocks?

Ruby's way of dealing with this is being able to pass named blocks ("procs". Kinda. There is a difference, but it doesn't matter) as actual proper arguments to the function, so the syntax would kinda become like this (this is pseudocode now, I can't be arsed looking up the actual Ruby code):

someBlockToProcessWithTheFunctionality = function |someparameter|
    # stuff here
end

someResult = someFunctionality someParameter, someBlockToProcessWithTheFunctionality

In a more CFML-familiar syntax, we'd have this:

someBlockToProcessWithTheFunctionality = function(someparameter) {
    // stuff here
};

someResult = someFunctionality(someParameter, someBlockToProcessWithTheFunctionality);

Whaddya know? This is all already supported in existing CFML syntax. No need for any different syntax constructs at all.

And we get the best of both worlds. We can either predefine our someBlockToProcessWithTheFunctionality, or we can do it inline:

someResult = someFunctionality(someParameter, function(someparameter) {
    // stuff here
});

That's now very familiar indeed, isn't it? There's simply no need for special syntax. CFML already has the syntax to deal with these tag constructs. And it's widely understood, and has scope for usage variation, and is predictable (a function takes arguments and returns a result).

One of the Railo engineers (I think it was Micha, but it might have been Igal: I cannae remember) poo-pooed this approach because calling functions is slower compared to hard-coded language constructs. Well... sorry mate... that's your problem, not ours. If your code doesn't run fast enough, don't force unwieldy illogical syntax work arounds on us... just make your code faster. Don't work around your shortcomings by creating shithouse language constructs. I don't mean to trivialise any challenges the Lucee dev team has here, but I think my point is sound that this is for them to solve, not to work around at everyone else's expense.

Anyway, I just wanted to clarify that my suggestion of a more existing-syntax-friendly, functional approach to these constructs didn't come out of left field: I actually gave it a reasonable amount of thought and looked at what other languages did first, and then came up with a language design approach that actually made sense for CFML. I believe one should tread very cautiously indeed before introducing new language constructs into a language, and in this case I don't think due thought was given to it. Or the challenges the vendor devs faced were solved at our expense, not their own expense. Which was completely the wrong thing to do.

So, Adobe and Lucee... I think I make a reasonable case here as to why you should deprecate your attempts at "providing tags in script" via your current approaches, and take up this approach I float here. It fits better with the existing language implementation, and is just... not shit.

Thoughts?

--
Adam