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

Using Built-In Functions (BIF) As Callbacks In Adobe ColdFusion 2025.0.8

By
Published in

With update 8, Adobe ColdFusion 2025 introduced the ability to pass around built-in functions (BIFs) just like any other user defined function (UDF) or closure. BIFs can be stored in variables, passed out of scope, and used as a callback in both ColdFusion and Java interactions.

For example, we can use the built-in ucase method as the operator of the Array.map() member method:

<cfscript>

	values = [ "Sam", "Kit", "Alex" ];

	// Using the BIF (ucase) as the map operator.
	uValues = values.map( ucase );

	writeOutput( uValues.toList( ", " ) );

</cfscript>

Here, the ucase built-in function is being used to transform each element of the values array. And when we run this Adobe ColdFusion 2025.0.8 code, we get the following output:

SAM, KIT, ALEX

At first, this seems to make total sense. But, when you stop to think about how iteration methods — like .map() — work, the fact that this executes without error can start to feel surprising. When a map method, or an each method, or a reduce method is invoked, the method makes several arguments available to the callback execution:

  • Map: callback( value, index, collection )
  • Each: callback( value, index, collection )
  • Reduce: callback( reduction, value, index, collection )

In the demo, if the CFML runtime were naively using the ucase BIF as the callback, it would end up invoking it like this:

ucase( value, index, collection )

... which would have thrown an error since the ucase BIF only accepts one argument. But, as we saw, the demo worked perfectly fine.

This is because the CFML runtime is not performing a naive BIF invocation. Instead, it's coercing the set of arguments into a predictable shape based on the context of the invocation.


For Array.map() and List.map() contexts, the shape is a unary function:

( value ) -> newValue

For Array.filter(), Array.every(), Array.some(), and List.filter() contexts, the shape is a predicate function:

( value ) -> Boolean

For Array.each() and List.each() contexts, the shape is a unary consumer function:

( value ) -> void

For Array.reduce() and List.reduce() contexts, the shape is a binary function:

( reduction, value ) -> reduction

For Array.sort() and List.listSort() contexts, the shape is a comparator function:

( a, b ) -> int

Note: for reasons that I don't fully understand Query and Struct methods don't play nicely with BIF-as-callback invocations. Presumably it has to do with the order of the arguments (key vs. value) provided to the callback. But that's as much as I can glean from the documentation.


This is why our ucase-as-callback didn't throw an error — ColdFusion was invoking it as a unary function even though the callback should technically accept three arguments.

You can verify this constraint / translation by using the left BIF as a callback. The left() function takes two arguments:

left( input, count )

... which can line up correctly with the first two arguments of the Array.map() callback:

left( value, index )

However, if you try to use left as the callback, ColdFusion will throw an error that left() requires two arguments, but only one was provided. Which means that even though the callback invocation exposes three arguments, only one was provided by the runtime to the BIF-as-callback.

I'm not much into Function Programming (FP); but I believe the FP developers solve this kind of a parameter-invocation-mismatch by transforming one function into another function of explicit arity. For example, before passing left into the Array.map() callback, we could proxy it through a UDF that returns a closure that always expects two parameters:

<cfscript>

	values = [ "Olly", "Olly", "Oxen", "Free" ];

	mappedValues = values.map( asBinaryFunction( left ) );

	writeOutput( mappedValues.toList( ", " ) );

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

	/**
	* I ensure the given operator is invoked as a binary function.
	*/
	private function function asBinaryFunction( required function operator ) {

		return ( a, b ) => operator( a, b );

	}

</cfscript>

Now, instead of passing left directly, we're passing asBinaryFunction( left ) as the callback. This proxy closure takes the three arguments made available to all Array.map() callbacks and only-and-always passes the first two onto the left invocation. Which means when we run this ColdFusion code, the .map() incrementally slices of an additional character from each element:

O, Ol, Oxe, Free

As you can see, each mapped element is the left-prefix based on the index value of the operator iteration.

This last example also demonstrates that BIFs can be passed around, assigned to variables / arguments, and then invoked later.

I'm not sure when or if I'll ever need to use ColdFusion built-in functions as callbacks. But, it's good to know that this now exists in the language. More likely, I'll use the BIF-as-variables for conditional invocations.

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

Reader Comments

Post A Comment — I'd Love To Hear From You!

Post a Comment

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
Managed ColdFusion hosting services provided by:
xByte Cloud Logo