Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with:

ColdFusion Ordered ArgumentCollection Behavior Depends On ColdFusion Version And Invocation Context

By Ben Nadel on
Tags: ColdFusion

Over the weekend, Martijn van der Woud told me that he could not use an ArgumentCollection approach to invoke a method using "ordered" arguments. Normally, I wouldn't question this; but, he happend to have made this comment on a blog post that demonstrated how to use the ArgumentCollection to invoke a method using ordered arguments. I decided to dig into this issue and discovered that ColdFusion's ArgumentCollection object behaves differently depending on the version of ColdFusion, the function invocation context, and the target function signature.

Ok, there's a good number of variables at play here, so I am going to break this down by ColdFusion version, invocation approach, and method signature. Let's start with ColdFusion 8. The first method signature that I'm going to look at has no CFArgument tags; it will defer to the calling context to create the argument associations.

EchoArguments - ColdFusion 8 Without CFArgument Tags

  • <cffunction
  • name="echoArguments"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I simply return the arguments collection to demonstrate their structure to the calling context.">
  •  
  • <!---
  • NOTE: In this demo, we are not going to define any
  • CFArgument tags - we are just going to let the invocation
  • context define the arguments.
  • --->
  •  
  • <!--- Return the arguments. --->
  • <cfreturn arguments />
  • </cffunction>

As you can see, this function simply returns the Arguments collection that ColdFusion defined for it.

Now, we are going to invoke the echoArguments() method using ColdFusion 8 and both the CFInvoke tag and the argumentCollection argument approaches.

  • <h1>
  • CF8 - No CFArgument Tags
  • </h1>
  •  
  •  
  • <!---
  • Create the argument collection at a struct using "ordered" keys.
  • Since the Arguments of a function can be addressed as both named
  • and ordered arguments, this allows for some awesome flexability.
  • --->
  • <cfset myArguments = {} />
  • <cfset myArguments[ 1 ] = "Katie (1st Arg)" />
  • <cfset myArguments[ 2 ] = "Colleen (2nd Arg)" />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Now, we are going to try and echo the argument collection we
  • just created using the CFInvoke tag.
  • --->
  • <h2>
  • CFInvoke Tag Invocation
  • </h2>
  •  
  • <!--- Pass in the arguments collection. --->
  • <cfinvoke
  • returnvariable="result"
  • method="echoArguments"
  • argumentcollection="#myArguments#"
  • />
  •  
  • <!--- Output the results. --->
  • <cfdump
  • var="#result#"
  • label="CFInvoke Invocation"
  • />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Now, we are going to try and echo the argument collection using
  • standard method invocation and the argumentCollection command.
  • --->
  • <h2>
  • Method Invocation
  • </h2>
  •  
  • <!--- Pass in the arguments collection. --->
  • <cfset result = echoArguments( argumentCollection = myArguments ) />
  •  
  • <!--- Output the results. --->
  • <cfdump
  • var="#result#"
  • label="Method Invocation"
  • />

As you can see, we are using the dual nature (named vs. ordered) of method arguments in order to create an argumentCollection using ordered arguments; rather than using proper names as argumentCollection keys, we are using index values for order-based invocation. When we run the above code, we get the following output:

 
 
 
 
 
 
ColdFusion ArgumentCollection Behavior In Different Versions And Calling Contexts. 
 
 
 

As you can see, ColdFusion 8, with no CFArgument tags, works as expected using both the CFInvoke tag and the argumentCollection argument approach.

Now, let's add some CFArgument tags to our method signature:

EchoArguments - ColdFusion 8 With CFArgument Tags

  • <cffunction
  • name="echoArguments"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I simply return the arguments collection to demonstrate their structure to the calling context.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="girl1" />
  • <cfargument name="girl2" />
  •  
  • <!--- Return the arguments. --->
  • <cfreturn arguments />
  • </cffunction>

With these CFArguments in place, we are going to run the same exact code above (with a minor H1 change for output clarity). This time, however, we get the following output:

 
 
 
 
 
 
ColdFusion ArgumentCollection Behavior In Different Versions And Calling Contexts. 
 
 
 

Things are starting to get a little strange. Once we add the CFArgument tags to ColdFusion 8, the CFInvoke approach continues to work; however, the method invocation, with an argumentCollection argument, now goes completely non-sensical.

Lessons from ColdFusion 8: Ordered arguments work with argumentCollection as long as no CFArgument tags are defined. Once CFArgument tags are defined, using an ordered argumentCollection approach only works with CFInvoke.

Now that we've seen how ColdFusion 8 works, let's try this all again with ColdFusion 9. I actually don't have the ColdFusion 9.0.1 updater installed yet, so we have a chance to view this pre and post updater.

Just as before, we are going to start with a method signature that doesn't have any CFArgument tags:

EchoArguments - ColdFusion 9 Without CFArgument Tags

  • <cffunction
  • name="echoArguments"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I simply return the arguments collection to demonstrate their structure to the calling context.">
  •  
  • <!---
  • NOTE: In this demo, we are not going to define any
  • CFArgument tags - we are just going to let the invocation
  • context define the arguments.
  • --->
  •  
  • <!--- Return the arguments. --->
  • <cfreturn arguments />
  • </cffunction>

As a refresher, I'm going to output the code from above; but, trust me in that it hasn't changed at all (minus an H1 tag change for output clarity):

  • <h1>
  • CF9 (No Updater) - No CFArgument Tags
  • </h1>
  •  
  •  
  • <!---
  • Create the argument collection at a struct using "ordered" keys.
  • Since the Arguments of a function can be addressed as both named
  • and ordered arguments, this allows for some awesome flexability.
  • --->
  • <cfset myArguments = {} />
  • <cfset myArguments[ 1 ] = "Katie (1st Arg)" />
  • <cfset myArguments[ 2 ] = "Colleen (2nd Arg)" />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Now, we are going to try and echo the argument collection we
  • just created using the CFInvoke tag.
  • --->
  • <h2>
  • CFInvoke Tag Invocation
  • </h2>
  •  
  • <!--- Pass in the arguments collection. --->
  • <cfinvoke
  • returnvariable="result"
  • method="echoArguments"
  • argumentcollection="#myArguments#"
  • />
  •  
  • <!--- Output the results. --->
  • <cfdump
  • var="#result#"
  • label="CFInvoke Invocation"
  • />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Now, we are going to try and echo the argument collection using
  • standard method invocation and the argumentCollection command.
  • --->
  • <h2>
  • Method Invocation
  • </h2>
  •  
  • <!--- Pass in the arguments collection. --->
  • <cfset result = echoArguments( argumentCollection = myArguments ) />
  •  
  • <!--- Output the results. --->
  • <cfdump
  • var="#result#"
  • label="Method Invocation"
  • />

As you can see, nothing has changed. We are still using the argumentCollection object to invoke the method using ordered arguments. This time, however, we get a very different output:

 
 
 
 
 
 
ColdFusion ArgumentCollection Behavior In Different Versions And Calling Contexts. 
 
 
 

As you can see, in ColdFusion 9 with no CFArgument tags, this breaks in both the CFInvoke and the method invocation approaches. While it might appear to work at first glance, notice that the arguments are actually arriving in reverse order.

Now, let's add the CFArgument tags back into our method signature to see if they alter ColdFusion 9's argumentCollection behavior:

EchoArguments - ColdFusion 9 With CFArgument Tags

  • <cffunction
  • name="echoArguments"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I simply return the arguments collection to demonstrate their structure to the calling context.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="girl1" />
  • <cfargument name="girl2" />
  •  
  • <!--- Return the arguments. --->
  • <cfreturn arguments />
  • </cffunction>

With these CFArguments in place, we are going to run the same exact code above (with a minor H1 change for output clarity). This time, we get a more agreeable outcome:

 
 
 
 
 
 
ColdFusion ArgumentCollection Behavior In Different Versions And Calling Contexts. 
 
 
 

As you can see, in ColdFUsion 9 with CFArgument tags in place, the argumentCollection object starts to behave as expected. Well, at least a bit more like it should; after all, if we're going to be using ordered arguments, the requirement of named CFArgument tags seems mostly inappropriate.

Lessons from ColdFusion 9 (no updater): Ordered arguments work with the argumentCollection if, and only if, named CFArgument tags are defined. Which is really to say, ordered arguments do not work in conjunction with the argumentCollection object in ColdFusion 9.

Now that I have tested this with ColdFusion 8 (8.0.1) and ColdFusion 9 (no updater), I am going to install the ColdFusion 9.0.1 updater to see if affects the argumentCollection behavior at all.

NOTE: After installing the ColdFusion 9.0.1 updater, I found no difference in argumentCollection behavior. As such, I'm not going to bother showing the code or the page output.

NOTE: I also installed the Cumulative Hotfix for 9.0.1 and did not see any changes in behavior.

One of the most powerful features of ColdFusion is the flexibility with which it can be used. Its dynamic nature allows developers to adapt it to almost any use case. Unfortunately, it looks like the argumentCollection - a cornerstone of ColdFusion's flexibility - has started to present some bugs in ColdFusion 9. Hopefully these will be ironed out in a future updater.




Reader Comments

Wow Ben you really dived into this, thanks!

In my case I am calling a function without cfargument tags (it is the $results() method of the mockbox.system.testing.MockBox class in the awesome (!) mocking framework by the equally awesome Luis Majano :)). I am on Railo and also do not get the arguments in the correct order, when I use a method invocation with (argumentcollection=...). Haven't tried cfinvoke yet.

Bottom line for me is that the behavior of cfargumentcollection in terms of ordering arguments varies too much across engines and versions to be relied upon for making anything that is maintainable in the long run, or compatible across platforms/engines.

In a way, it makes sense though. After all, argumentcollection is a Struct. Structs are un-ordered collections.

Anyway thanks for the exploration, this is interesting stuff!

Reply to this Comment

@Martijn,

Yeah, this is really a shame. True, the argumentCollection is a struct; but, the Arguments scope is not. It allows both key-access and ordered-access. It's pretty awesome! And, the fact that you can invoke a method with dynamic, ordered arguments has actually come in handy a number of times for me.

I'd rather see them fix it than not using it.

As far as different engines go, at some point, I guess you just gotta pick a horse and ride it. After all, it makes sense to leverage the power of the different engines.

Reply to this Comment

Hi Ben
I'm sorry, but I have to disagree with pretty much everything you've posted here, right from the set-up.

When you create a *struct* and load it with keys "1" and "2", passing it into your test function, you are not passing "ordered arguments", you are passing named arguments. It's just that the names are "1" and "2". If you were to set out to pass *ordered* arguments, then you'd need to pass an array into argumentscollection, which one cannot do (although it would be handy if one could).

Structs are intrinsically unordered. By definition. It matters not if you give the keys numeric names, any ordering you perceive (like if dumping them out, they appear in some order) is completely coincidental to the data structure. That is just an artefact of their implementation, or more like that <cfdump> is getting a structKeyList() or ~array() and sorting it before outputting the key values.

They behaviour you are seeing with the <cfargument> tags is predictable and correct in CF8: an argument called "1" is a different argument from one called "girl1", so they should be treated differently, and there is no way the value of the struct key "1" should be applied to the argument named "girl1". Basically you're saying your function takes two named args: girl1, girl2, then passing in two *different* named args: "1" and "2". You'd see exactly the same thing if you used keys "boy1" and "boy2" in your initial struct: they would no more implant their values into the girl-named args than your "1" and "2" args would. Except you would not be surprised about that: CF allows one to pass any arguments into a function, be they defined by <cfargument> tags or not. But there is no way that a struct key called "1" should ever map to a <cfargument> names "girl1".

It does seem, though, like you've uncovered a bug in CF9: it should not behave the way it does (for the reasons outlined above), and it ought to be fixed. You should raise a bug:

http://cfbugs.adobe.com/cfbugreport/flexbugui/cfbugtracker/main.html#

--
Adam

Reply to this Comment

@Adam,

Just to get one point out of the way, I completely agree with you that structs have no inherent order - that any order that structs happen to display on their own is simply a matter of coincidence and is not something that should be used as a "feature."

That said, the Arguments collection is definitely a special type of object. I think we can agree that it allows for both named AND ordered references. That is, I can use both of these with success:

  • <cfargument name="foo" />
  • #arguments.foo#
  • #arguments[ 1 ]#

This is the expected, documented behavior of the Arguments collection.

Because of this, I hypothesize that ColdFusion is doing what it can to map the non-ordered Struct to the ordered Arguments collection.

Now, I think I can show here that this hypothesis is correct by looking at the way my original examples handles "1" and "2" vs. "boy1" and "boy2". So, just to get us all on the same page, here is my echo function for this test:

  • <cffunction name="echoArguments">
  • <cfargument name="girl1" />
  • <cfargument name="girl2" />
  •  
  • <cfreturn arguments />
  • </cffunction>

All we're doing is defining the two arguments "girl1" and "girl2" and then echoing back whatever is being passed into the function.

Ok, so now, let's re-test the "1" and "2" approach to make sure this works:

  • <cfset myArguments = {} />
  • <cfset myArguments[ "1" ] = "Katie (1st Arg)" />
  • <cfset myArguments[ "2" ] = "Colleen (2nd Arg)" />
  •  
  • <cfinvoke
  • returnvariable="result"
  • method="echoArguments"
  • argumentcollection="#myArguments#"
  • />
  •  
  • <cfdump
  • var="#result#"
  • label="CFInvoke Invocation"
  • />

As you can see, I'm simply doing what I did before. And, when I output the CFDump, I get the following struct (ascii art style):

[ GIRL1 ][ Katie (1st Arg) ]
[ GIRL2 ][ Colleen (2nd Arg) ]

Ok, so in this case, the arguments "1" and "2" mapped to "girl1" and "girl2".

Now, going back to what you were saying - if I do this with "boy1" and "boy2" rather than "1" and "2", I should expect to get the same result (if I understand what you are saying):

You'd see exactly the same thing if you used keys "boy1" and "boy2" in your initial struct: they would no more implant their values into the girl-named args than your "1" and "2" args would.

So, here's what I am doing this time:

  • <cfset myArguments = {} />
  • <cfset myArguments[ "boy1" ] = "Katie (1st Arg)" />
  • <cfset myArguments[ "boy2" ] = "Colleen (2nd Arg)" />
  •  
  • <cfinvoke
  • returnvariable="result"
  • method="echoArguments"
  • argumentcollection="#myArguments#"
  • />
  •  
  • <cfdump
  • var="#result#"
  • label="CFInvoke Invocation"
  • />

As you can see, I have simply replaced "1" with "boy1" and "2" with "boy2". However, this time, we get the following echoed arguments collection:

[ GIRL1 ][ undefined struct element ]
[ GIRL2 ][ undefined struct element ]
[ boy1 ][ Katie (1st Arg) ]
[ boy2 ][ Colleen (2nd Arg) ]

As you can see, this time, boy1 and boy2 were passed in as additional arguments; they were not mapped to the first and second arguments as "1" and "2" were, respectively.

If ColdFusion wasn't mapping ordered "keys" to ordered "arguments", we would expect to get the same results in either case; however, we clearly do not. And again, I posit that this is because ColdFusion realizes that is cannot map boy1 and boy2 as ordered arguments, so it defaults to mapping them as additional named arguments.

Reply to this Comment

... Just as a followup, I fired up CF9 to test what I just commented (I was only testing in CF8 for my response). This same behavior is also confirmed in CF9 as well (boy1 and boy2 getting appended to the arguments collection rather than mapped).

To me, this *feels* like a feature; but, it might be a bug - one that they haven't caught yet.

I think we both agree that the *four* undefined struct elements in CF9 is a bug; that behavior makes no sense at all - I will file if for no other reason to get some feedback.

Reply to this Comment

Right. Sorry, we're speaking @ cross-purposes (slightly), and it's my fault.

The code I was running before (since reused for something else... my reply to your other blog post, so I don't have it now) was a close variation on yours in which I got:

numeric arg names: function notation - struct
1: [arg 1 value]
2: [arg 2 value]
NAMEDARG1: [undefined struct element]
NAMEDARG2: [undefined struct element]

This is with a numerically keyed struct passed into a function with two declared args.

And that's what I'd expect to see with your code, and what I consider to be correct.

However I just ran *exactly* your code (argument names and struct values changed to gender-neutralise it) on CF8&9 with both function and <cfinvoke> notation... and see that neither CF8 nor CF9 are actually behaving what I'd consider coherently. But at least CF9 has uniform results!

You're right that "arguments" is a weird blend of struct and array, but now I think it's even more stuffed up and I initially thought it was!

Here's my test code, btw:

<cfset args = {}>
<cfset args[1] = "arg 1 value">
<cfset args[2] = "arg 2 value">

<cffunction name="echoArguments">
<cfargument name="namedArg1">
<cfargument name="namedArg2">
<cfreturn arguments>
</cffunction>

<cfset result = echoArguments(argumentCollection=args)>
<cfdump var="#result#" label="numeric arg names: function notation" format="text">

<cfinvoke method="echoArguments" argumentcollection="#args#" returnvariable="result2">
<cfdump var="#result2#" label="numeric arg names: cfinvoke" format="text">

<cfset args2 = {}>
<cfset args2["arg1"] = "arg 1 value">
<cfset args2["arg2"] = "arg 2 value">

<cfset result3 = echoArguments(argumentCollection=args2)>
<cfdump var="#result3#" label="string arg names: function notation" format="text">

<cfinvoke method="echoArguments" argumentcollection="#args2#" returnvariable="result4">
<cfdump var="#result4#" label="string arg names: cfinvoke" format="text">

And CF8 output:
numeric arg names: function notation - struct
1: [undefined struct element]
2: [undefined struct element]
NAMEDARG1: [undefined struct element]
NAMEDARG2: [undefined struct element]

numeric arg names: cfinvoke - struct
NAMEDARG1: arg 1 value
NAMEDARG2: arg 2 value

string arg names: function notation - struct
NAMEDARG1: [undefined struct element]
NAMEDARG2: [undefined struct element]
arg1: arg 1 value
arg2: arg 2 value

string arg names: cfinvoke - struct
NAMEDARG1: [undefined struct element]
NAMEDARG2: [undefined struct element]
arg1: arg 1 value
arg2: arg 2 value

And CF9 output:
numeric arg names: function notation - struct
NAMEDARG1: arg 1 value
NAMEDARG2: arg 2 value

numeric arg names: cfinvoke - struct
NAMEDARG1: arg 1 value
NAMEDARG2: arg 2 value

string arg names: function notation - struct
NAMEDARG1: undefined
NAMEDARG2: undefined
arg1: arg 1 value
arg2: arg 2 value

string arg names: cfinvoke - struct
NAMEDARG1: undefined
NAMEDARG2: undefined
arg1: arg 1 value
arg2: arg 2 value

I wish I could recall what code I ran that gave me my original (correct in CF8, wrong in CF9 output). Oh well.

--
Adam

Reply to this Comment

@Adam,

Regardless of how we individually feel about the ordered-argument mapping, I think we can both agree that the behavior of the argumentCollection with anything but named-arguments is simply not consistent enough :) CF9 makes it more consistent (within the range of CF9 examples), but it loses any of the non-named-argument "value."

So, in the end, whether I like it or not, ordered-argument mapping is simply not viable in ColdFusion 9.

Reply to this Comment

Hi Ben,

Good discussion. I recall playing w/ this ordered-argument mapping a while back. I also tried passing the numbered struct as a LinkedHashMap and a TreeMap, to see if I'd get any different behavior. I believe I ended up just creating an object, and then passing the args as a list. Then it didn't matter if the function had arguments defined or not.

i.e.: myObj.echoArguments('Katie (1st Arg)','Colleen (2nd Arg)');

FWIW, there's an ER for dynamic function invocation in script: http://cfbugs.adobe.com/cfbugreport/flexbugui/cfbugtracker/main.html#bugId=79390

Thanks!,
-Aaron

Reply to this Comment

@Aaron,

It's a cool "feature". Typically, you don't need to use it as the requirement of non-determinant ordered arguments is a very rare case; but, from time to time it does crop up. You can always fall back on the evaluate() approach; but, I love when you can do something more semantic if possible.

Reply to this Comment

Adam is unfortunately wrong that this is a bug. CF 8.0.1 intentionally added this behavior to cfinvoke to allow name="{number}" or argumentCollection on cfinvoke to use numbered keys so that frameworks could invoke methods without needing to know the name of the arguments. It was a really big feature at the time, and one of the reasons to update to 8.0.1 off the original CF8 release.

Adobe *intentionally* made argumentCollection consistent in normal invocations in CF9 instead of having the special case feature only available in cfinvoke which is rather useless in new all script code.

Neither of these behavior changes were bugs. They were features that were done on purpose and they make sense. Say I want to call down through a proxy to a function in this manner:

function test(a, b) {
return a + b;
}

function proxy() {
// here we have arguments["1"] and arguments["2"] defined just like CF9
// and argumentCollection remaps them to the positional args
// just fine in the first example below
return test(argumentCollection=arguments);
}

// This calls proxy with numerically named arguments
// and works in CF8+
five = proxy(2, 3);

args = {};
args["1"] = 1;
args["2"] = 2;
// This doesn't work in CF8, wtf?
three = proxy(argumentCollection=args);

<!--- This does work in CF8.0.1+ --->
<cfinvoke component="this" method="proxy" returnvariable="three" argumentCollection="args">

This inconsistency just didn't make sense in CF8.0.1. Arguments is already magical so that if I call a function with positional arguments and pass the arguments variable in argumentCollection to another function it'll remap them. Why shouldn't I be able to do this manually by creating a struct?

Adam's argument about arguments (hah!) just doesn't hold up, especially when you consider multiple levels of proxies:

function proxy1() {
return test(argumentCollection=arguments);
}

function proxy2() {
// So now arguments is a "struct" and I can use argumentCollection
// but when I first called it I had to use an array? This makes no sense.
return proxy1(argumentCollection=arguments);
}

proxy2(argumentArray=[1,2,3]);

CF9's behavior is not exceptional at all. It's consistent with how users have always expected the arguments collection to work. Most people just didn't think about how using argumentCollection=arguments remapped names before.

Reply to this Comment

@Ben

There's some confusion in your premise that the mapping has to do with the "order" of the struct assignments. It has to do with the names.

args["2"] = 10;
args["1"] = 5;
test(argumentCollection=args); // same as test(5, 10);

This is the reason your boy1 and boy2 example isn't remapping the names. It remaps numerical struct keys. The order of assignment is totally irrelevant.

I think your statement that "ordered-argument mapping is simply not viable in ColdFusion 9." is wrong. It works just fine.

Reply to this Comment

@Elliott,

Thanks for the insight into the argumentCollection behavior in 8.0.1 and 9. I didn't catch this. However, as Ben and Martijn noted (and as I understood), the issue is not with functions which have arguments defined. Rather, the issue is with functions that do _not_ have arguments defined (or even have less arguments defined than are passed-in).

Example, please run this code in CF8.0.1 & CF9.0.1:

  • <cfscript>
  • function test() {
  • return(ARGUMENTS[1] - ARGUMENTS[2]);//4-2=?
  • }
  • args={};
  • args[1] = 4;
  • args[2] = 2;
  • </cfscript>
  • <cfinvoke method="test" returnvariable="myVar1" argumentcollection="#args#" />
  • <cfinvoke method="test" returnvariable="myVar2">
  • <cfinvokeargument name="1" value="#args.1#" />
  • <cfinvokeargument name="2" value="#args.2#" />
  • </cfinvoke>
  • <cfoutput>
  • #myVar1#<br />
  • #myVar2#<br />
  • #test(4,2)#
  • </cfoutput>

CF8.0.1 result:
2
2
2

CF9.0.1 result:
-2
-2
2

I don't feel ordered-argument mapping is working fine. How is this not a bug in CF9?

If it is, then I'd agree w/ Ben's statement, if it were slightly reworded: "So, in the end, whether I like it or not, ordered-argument mapping is simply not viable in ColdFusion 9[, when the called function has less arguments defined than are passed-in]."

If the argumentArray, which Adam suggested, did exist, then there would be a workaround to this bug. B/c even the proxy could still use argumentArray=ARGUMENTS (to force arg order to be maintained), couldn't it? And this argumentArray was something I'd thought about too (in my prior run-in w/ this issue). But, rather than add argumentArray, it appears this bug just needs fixed?

Thanks!,
-Aaron

Reply to this Comment

@Elliott,

It's nice to get a confirmation that the numeric-key to ordered-arguments mapping was a feature explicitly added to the language at 8.0.1. Very interesting explanation with the proxies!

And as far as the boy1 boy2 example, I was just trying to demonstrate to @Adam that order did not make a difference.

That said, as @Aaron pointed out, using numeric keys only works IF the arguments are explicitly defined with CFArgument tags. This, however, requires the calling context to know something about the inner implementation of the function. This could be considered a violation of encapsulation and potentially a bug.

I am not sure I see the value in the direction they chose, unless it was a technical limit. It seems like it would have been more flexible in the long term to get both use-cases (CFInvoke and CFScript) to allow numeric key mapping regardless of the argument definitions. That's why I assume it must have not been technically appealing.

Reply to this Comment

@Aaron

That does appear to be a bug in CF9. If there's not enough named arguments to map the positional args to it just iterates the structure assigning them to arguments which is broken since there's no order to a structure.

<cfscript>
function test() { return arguments; }
args = {"1"=1, "3"=3, "2"=2, "4"=4, "5"=5};
writeDump(test(argumentCollection=args));
// results in (3, 2, 1, 5, 4)
</cfscript>

The trivial workaround is to just use a TreeMap:

<cfscript>
function test() { return arguments; }
args = createObject("java", "java.util.TreeMap").init({"1"=1, "3"=3, "2"=2, "4"=4, "5"=5});
writeDump(test(argumentCollection=args));
// results in (1, 2, 3, 4, 5)
</cfscript>

You could also solve this inside your functions if necessary:

<cfscript>
function test() {
arguments = createObject("java", "java.util.TreeMap").init(arguments);
return arguments[1] - arguments[2];
}
writeDump(test(argumentCollection={"1"=4, "2"=2}));
// results in 2
</cfscript>

Reply to this Comment

@Elliott,

Very interesting! I know @Aaron mentioned TreeMap in one of his comments, but I've never seen these in action before. Looks very cool. Just so understand, a TreeMap is ordered by virtue of its keys, not by the order in which they are set (which is what a LinkedHashMap would do)?

Reply to this Comment

@Ben,

That's correct. TreeMap is key-ordered (ascending); LinkedHashmap is insert-ordered. http://vidyapsi.wordpress.com/2009/01/22/hashmap-vs-hashtable-vs-treemap-vs-linkedhashmap/

Regarding the bug, I wish I'd have filed it in 9.0.1, tho I was not yet sure it was a bug, and my recommendation at that time was different than it is now. I'd was just tinkering really, and didn't compare w/ CF8. Btw, thanks for posting that comment in the bug tracker w/ link to this thread (that's how I found this, and figured I'd dig in again). My recommendation (just notes in my test code, and never yet posted anywhere) was that argumentCollection should be treated as a LinkedHashmap (insert-ordered). However, this is not consistent w/ how arguments are ordered in CF8. Thus, Elliott's suggestion of TreeMap makes the most sense.

Also, I noticed Elliott passed a struct directly into TreeMap's init (rather than using "put"). In my tests, I'd used "put". Curious, I just tested both methods (initializing w/ struct vs "put") with both TreeMap and LinkedHashmap. TreeMap behaved the same w/ both methods. LinkedHashmap, however, did not. To illustrate, here is example code:

[code]
<cfscript>
args = {1="one-1", 3="two-3", 2="three-2", 4="four-4", 5="five-5"};
//Example #1: TreeMap w/ args in init
treeMapArgs = createObject("java", "java.util.TreeMap").init(args);
writeDump(test(argumentCollection=treeMapArgs));//result: one-1,three-2,two-3,four-4,five-5 (key order)
//Example #2: Treemap w/ args put in
treeMap = createObject("java", "java.util.TreeMap").init();
treeMap.put(1, "one-1");
treeMap.put(3, "two-3");
treeMap.put(2, "three-2");
treeMap.put(4, "four-4");
treeMap.put(5, "five-5");
writeDump(test(argumentCollection=treeMap));//result: one-1,three-2,two-3,four-4,five-5 (key order)
//Example #3: LinkedHashmap w/ args in init
linkedHashmapArgs = createObject("java", "java.util.LinkedHashMap").init(args);
writeDump(test(argumentCollection=linkedHashmapArgs));//result: two-3,three-2,one-1,five-5,four-4 (unordered)
//Example #4: LinkedHashmap w/ args put in
linkedHashmap = createObject("java", "java.util.LinkedHashMap").init();
linkedHashmap.put(1, "one-1");
linkedHashmap.put(3, "two-3");
linkedHashmap.put(2, "three-2");
linkedHashmap.put(4, "four-4");
linkedHashmap.put(5, "five-5");
writeDump(test(argumentCollection=linkedHashmap));//result: one-1,two-3,three-2,four-4,five-5 (insertion order)
</cfscript>
[/code]

However, as mentioned in my 1st post, I ended up just doing this in CF9 (instead of messing w/ argumentCollection):

[code]
//Example #5: short-n-sweet
writeDump(test("one","two","three","four","five"));//result: one,two,three,four,five
[/code]

Honestly, I haven't used this ordered-argument stuff in real-life code. Sometimes, like you, I just like to tinker w/ the toys :)

Also note that I'm doing "writeDump()" and not "writeDump();" The ";" isn't required after writeDump(). ;) (not certain that will be forward-compatible tho)

I'd like to add my opinion here.. (esp if Adobe's listening) If arguments is to be treated as both an array and a struct, within a function, then arguments should be able to be passed into a function as both an array and a struct. (aka Adam's argumentArray)

Also, I don't understand why we don't have cfinvoke functionality in cfscript. There's "_invoke", but it requires 3 to 3 arguments (essentially, it requires that the 1st argument be an object - instead of also having ability to call function in the current .cfm).

Example: _invoke(new MyCFC(), "myFunction", {1="one",2="two"});

So, in Marks' bug #79390, I suggested this format:

myVar = new MyCFC("bla")[methodName]("one","two","three");

That'd pass "bla" to the init, and support dynamic function name, and ordered arguments all in 1 line. That'd rock! ..at least, I think so.. unless anyone has any ideas on why that wouldn't work. ..anyhow, that's just wishful thinking.

Thanks!,
-Aaron

Reply to this Comment

Hi Elliott.
Whether or not Adobe did it on purpose - I don't dispute you there, and it doesn't relly surprise me - it's that they did it *that way* that is "the bug" to me as much as anything else. As you go on to point out: structs aren't ordered. So then to decide "although if you give them numeric key names, we'll magically decide to treat them as ordered" is a daft solution to what they were trying to effect.

If they want to be able to pass a collection of ordered args into a method, they should have implemented it via being able to pass an array in, not a struct with conveniently named keys.

--
Adam

Reply to this Comment

@Aaron,

It took me a minute to figure out what you were doing with the ordering - at first, I didn't realize that you were putting the key "name" after the key value. But, I guess the outcome with the LinkedHashMap make sense since the map doesn't have linked behavior *until* you create the LinkedHashMap instance; as such, all the inserts done before that would be lost.

This all such interesting stuff. After you guys brought up the TreeMap, I did some Googling and actually ordered a used book called "Java for ColdFusion developers". I think the book is like 7 years old or something (and maybe a version of Java behind); but, it seems like it would be a great resource to just thumb through. There appears to be a free version of it online... I can't read books at a computer.

As far as the notation:

component[ method ]( args )

... when I lasted talked to Rupesh Kumar, he had thought that they might have already fixed that in 9.0.1. I haven't tested it; but if he thinks that it may have been fixed, then at the very least, it is on their radar as something that needs to be done.

@Adam,

I'll agree with you that having something like an argumentArray would feel a bit more "right".

Reply to this Comment

Hi Ben (and Elliott).
Yeah. I don't quite see Elliott's point re this:

[quote]
// So now arguments is a "struct" and I can use argumentCollection
// but when I first called it I had to use an array? This makes no sense.
[/quote]

And I hasten to add that when I say I don't see Elliott's point, I don't mean "I don't see that he has one", I just don't get what he means. And indictment of *me*, not him.

This whole thing is based on needing a mechanism to get both named & ordered arguments INTO the function. But once it's in the function, it's a coldfusion.runtime.argumentscope or whatever it is: it's neither a struct nor an array in the CF sense of the ideas.

I guess, what's been implemented is that the thing that loads the coldfusion.runtime.argumentscope instance will only accept a CF struct, and it has special handling for numbered struct keys. This "special handling" seems like the wrong approach to me. What I'm driving at is that perhaps there should be two "constructors": one taking a CF struct for named args, and one taking an array for ordered args. The end game is the same for both: getting the values into the argumentscope object. But once it's *in* the argumentscope object, that's actually what we wanted in the first place, and passing that around the place from functionA() to functionB() is not problematic, is it?

--
Adam

Reply to this Comment

@Adam,

And, to your point, since CF9 is *already* not backward compatible with CF8 behavior in this regard, it's really the perfect time to have them implement both an argumentCollection and an argumentArray. They can't say it would break backwards acceptability or anything since... well, since they already did that :)

Reply to this Comment

@All,

It looks like the TreeMap approach only works in ColdFusion 9. I still get bad behavior when using TreeMap to try to map ordered arguments to explicit named arguments in CFScript invocation.

But, it does work in ColdFusion 9.

Reply to this Comment

... oops, just to clarify my last comment - TreeMap doesn't fully work in CF8. It will map ordered arguments to implicit arguments (no CFArgument tags); but, it will not map ordered arguments to explicit tags.

Reply to this Comment

I've been playing around with this for quite some time now and Elliot's double proxy example shows exactly where the problem arises.
Therefore I totally agree with Adam's point:

"If they want to be able to pass a collection of ordered args into a method, they should have implemented it via being able to pass an array in, not a struct with conveniently named keys"

IMHO Adobe should implement a native cf-collection-object (OrderedStructNew()) that represents a "hashmap with ordered keys". I do understand though that choosing between several implementations natively available in Java as the default native could bring op burdens. But if it would only be there, my life wouldn't be this sad.

Okay, okay, my life isn't that sad, but you get the point :)

Reply to this Comment

@DeepDown,

I know ordered-structs is something that a lot of people ask for; but, outside of invoking methods, I am not so sure I see that many use cases. I am not saying they are not there (clearly people *are* asking for it). What kind of ways do you see something like this being useful?

I think Marc Esher had an example of needing to digitally sign a request with ordered arguments (for things like oAuth).

Reply to this Comment

Hey all, not to kick dead horses, but the Adobe Dev Team just sent me an email from their Bug Database thing in order to confirm that this *was* buggy behavior.

It doesn't say *what* the expected behavior is; it only states that a bug was demonstrated. We'll see what happens in the next release of ColdFusion.

Reply to this Comment

Thank your very much for exploring the bug Ben! I've been struggling with this for a few hours now and it really drives me nuts! :(

We need the ColdFusion 8 behaviour on a ColdFusion 9.0.1 box, since we rely on the builtin ORM.

So are there any news, bugfixes or workarounds out there yet?

Reply to this Comment

@Ben

Regarding: "We'll see what happens in the next release of ColdFusion."

I tried to post a code sample here, but it wouldn't let me. I've emailed it to you via your contact form.

Thanks,
-Aaron

Reply to this Comment

@Ben, Didn't see a reply so posting here. CF10's invoke() accepts an argumentArray:

<cfscript>
function f() {return ARGUMENTS;}
argumentArray = [1,2,3,4,5];
writeDump(invoke("", "f", argumentArray));
</cfscript>

Positional order is maintained.

HTH!,
-Aaron

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.