Skip to main content
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Matthew Eash
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Matthew Eash ( @mujimu )

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

By on
Tags:

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.

Want to use code from this post? Check out the license.

Reader Comments

55 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.

15,663 Comments

@Zac,

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

45 Comments

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

426 Comments

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!

15,663 Comments

@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.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel