Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Built-In Functions And Member-Methods Return Different Data Types In Lucee CFML 5.3.5.92

By Ben Nadel on
Tags: ColdFusion

The other day, when I was exploring the ProcessBuilder class in Lucee CFML, I coded something that should have been a bug; but, that ended-up working. In fact, I didn't even see the issue until after I had posted the article. In my code, I treated the result of an array.append() member-method call as an Array. But, this Array member-method is documented as returning a Boolean. After digging into this a bit more, it turns out that built-in functions and member-methods sometimes return different data types in Lucee CFML 5.3.5.92.

To explore the difference in behavior, I created two demo scripts that compare the built-in functions to the member-methods for two complex data types:

  • Array
  • Struct

Really, I only care about this from an Array stand-point; however, I figured it would be interesting to see if the same divergent behavior exists in Structs as well.

Here's my test for Arrays:

<cfscript>

	values = [ "noice" ];

	echo( "<strong> Checking append() </strong><br />" );
	echo( "ArrayAppend() : " & typeOf( arrayAppend( values, "woot" ) ) & "<br />" );
	echo( "Array.append() : " & typeOf( values.append( "woot" ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong>Checking append( merge ) </strong><br />" );
	echo( "ArrayAppend( merge ) : " & typeOf( arrayAppend( values, [ "woot" ], true ) ) & "<br />" );
	echo( "Array.append( merge ) : " & typeOf( values.append( [ "woot" ], true ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong>Checking prepend() </strong><br />" );
	echo( "ArrayPrepend() : " & typeOf( arrayPrepend( values, "woot" ) ) & "<br />" );
	echo( "Array.prepend() : " & typeOf( values.prepend( "woot" ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong>Checking prepend( merge ) </strong><br />" );
	echo( "ArrayPrepend( merge ) : " & typeOf( arrayPrepend( values, [ "woot" ], true ) ) & "<br />" );
	echo( "Array.prepend( merge ) : " & typeOf( values.prepend( [ "woot" ], true ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong>Checking deleteAt() </strong><br />" );
	echo( "ArrayDeleteAt() : " & typeOf( ArrayDeleteAt( values, 1 ) ) & "<br />" );
	echo( "Array.deleteAt() : " & typeOf( values.deleteAt( 1 ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong>Checking delete() </strong><br />" );
	echo( "ArrayDelete() : " & typeOf( ArrayDelete( values, "noice" ) ) & "<br />" );
	echo( "Array.delete() : " & typeOf( values.delete( "noice" ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong>Checking insertAt() </strong><br />" );
	echo( "ArrayInsertAt() : " & typeOf( ArrayInsertAt( values, 1, "cool" ) ) & "<br />" );
	echo( "Array.insertAt() : " & typeOf( values.insertAt( 1, "cool" ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong>Checking resize() </strong><br />" );
	echo( "ArrayResize() : " & typeOf( ArrayResize( values, 10 ) ) & "<br />" );
	echo( "Array.resize() : " & typeOf( values.resize( 10 ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong>Checking set() </strong><br />" );
	echo( "ArraySet() : " & typeOf( ArraySet( values, 9, 10, "player" ) ) & "<br />" );
	echo( "Array.set() : " & typeOf( values.set( 9, 10, "player" ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong>Checking Swap() </strong><br />" );
	echo( "ArraySwap() : " & typeOf( ArraySwap( values, 1, 10 ) ) & "<br />" );
	echo( "Array.swap() : " & typeOf( values.swap( 2, 9 ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong>Checking clear() </strong><br />" );
	echo( "ArrayClear() : " & typeOf( arrayClear( values ) ) & "<br />" );
	echo( "Array.clear() : " & typeOf( values.clear() ) & "<br />" );
	echo( "<br />" );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I return the data-type of the given value (limited outcomes for this demo).
	* 
	* @value I am the value being tested.
	*/
	public string function typeOf( any value ) {

		if ( isArray( value ) ) {

			return( "Array" );

		} else if ( isBoolean( value ) ) {

			return( "Boolean" );

		} else {

			throw( type = "Unexpected demo value." );

		}

	}

</cfscript>

As you can see, I'm just looking at the data type returned by each version of the tested Array methods. And, I'm doing the same for Structs:

<cfscript>
	
	data = {
		hello: "world"
	};

	echo( "<strong> Checking append() </strong><br />" );
	echo( "StructAppend() : " & typeOf( structAppend( data, { foo: "bar" } ) ) & "<br />" );
	echo( "Struct.append() : " & typeOf( data.append( { meep: "moop" } ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong> Checking insert() </strong><br />" );
	echo( "StructInsert() : " & typeOf( structInsert( data, "das", "boot" ) ) & "<br />" );
	echo( "Struct.insert() : " & typeOf( data.insert( "boot", "das" ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong> Checking update() </strong><br />" );
	echo( "StructUpdate() : " & typeOf( structUpdate( data, "das", "cool" ) ) & "<br />" );
	echo( "Struct.update() : " & typeOf( data.update( "boot", "cool" ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong> Checking delete() </strong><br />" );
	echo( "StructDelete() : " & typeOf( structDelete( data, "foo" ) ) & "<br />" );
	echo( "Struct.delete() : " & typeOf( data.delete( "meep" ) ) & "<br />" );
	echo( "<br />" );

	echo( "<strong>Checking clear() </strong><br />" );
	echo( "StructClear() : " & typeOf( structClear( data ) ) & "<br />" );
	echo( "Struct.clear() : " & typeOf( data.clear() ) & "<br />" );
	echo( "<br />" );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I return the data-type of the given value (limited outcomes for this demo).
	* 
	* @value I am the value being tested.
	*/
	public string function typeOf( any value ) {

		if ( isStruct( value ) ) {

			return( "Struct" );

		} else if ( isBoolean( value ) ) {

			return( "Boolean" );

		} else {

			throw( type = "Unexpected demo value." );

		}

	}

</cfscript>

Now, if we run both of these ColdFusion scripts in Lucee CFML 5.3.5.92, we get the output (I've composited both results into one image for easier viewing):

Built-in function return values being compared to member-method return values for Arrays and Structs in Lucee CFML.

As you can see, the member-methods tend to return the host-object (except for struct.delete()) whereas the built-in functions always return a Boolean value in order to indicate success.

To be clear, I am super excited for the observed member-method behavior. I never understood why ColdFusion returned Booleans to indicate success. That was a strange pattern that I'm sure no one ever used. Having the member-methods return the host object allows for more intuitive and fluent code. I'll file an issue for this - to fix the documentation - which currently shows all of these methods as returning Boolean values.


Reader Comments

but you'll need to compile and run your own version of lucee to test / see those values.... it's fine to edit those files without compiling, the automatic CI process after you file a pull request will do a build

Docs reads them from directly from the version of Lucee you're running. using https://docs.lucee.org/reference/functions/getfunctiondata.html etc

As such changes to those files will only appear once merged and published as part of a stable release, and only after Brad updates Commandbox to default to that version.

Reply to this Comment

@Zac,

Ah, very interesting! I'll see if I can get that doc-serving going locally! Thanks for pointing me in the right direction.

Reply to this Comment

Yes, member functions "fixed" the return value in most cases to be the original object which is 1000 times more useful for chaining calls. However, the docs were largely just copied (or in some cases shared) with the headless functions. Another issue I've run into is the error messages for these functions will give the wrong information. For instance, "second argument should have been xyz" when in reality it's the first argument when being called as a member function. This due to the fact that the same underlying code powers both in many cases.

https://luceeserver.atlassian.net/browse/LDEV-2657

Reply to this Comment

That's really interesting.

I thought:

ArrayAppend()

Returned void

So, I usually write:

<cfset ArrayAppend(foo,"bar")>

I must say this does not throw an exception!

Reply to this Comment

@Charles,

To be honest, I have no idea why it even returns Boolean. I am sure there is some use-case where you can return False? Such as with overwriting keys in something like structInsert(). But, I've never understood the use-case for it.

Reply to this Comment

@All,

Zac Spitzer has already fixed the documentation! The Lucee team is just too cool for school :D

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
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.