Skip to main content
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Jason Dean
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Jason Dean

What If "Maybe" Was A Core Data Type In ColdFusion

By
Published in Comments (9)

For the last few years, I've been using the concept of a "maybe" value in my ColdFusion applications. In my CFML code, a "Maybe" is just an object that wraps a value that may or may not exist. The wrapper object exposes an exists property which indicates whether or not the value property is populated. Heretofore, I've been implementing this pattern as just that — a pattern; but now I'm trying to elevate the concept into more of a first-class citizen.

In my data-access layer (DAL), I'll often have maybeGet() and maybeGetByFilter() methods. These methods take a set of query parameters, query the database, and then return a "maybe" result. The code looks something like this:

component {

	public struct function maybeGet( required numeric id ) {

		var results = gateway.getByFilter( id = id );

		if ( results.len() ) {

			return {
				exists: true,
				value: results.first()
			};

		}

		return {
			exists: false
		};

	}

}

As you can see, if I get back a row from the database, the exists property is set to true and the value property contains the row. And, if no row is found, the exists property is set to false and no value property is provided. The calling context can then check the .exists condition before attempting to consume the .value property:

<cfscript>

	maybeUser = userModel.maybeGet( 1234 );

	if ( maybeUser.exists ) {

		gotoProfile( maybeUser.value.id );

	}

</cfscript>

This has made my ColdFusion code easier to read and to write (at least for me). But, the core notion of the "maybe" is just a struct literal that's been copy-pasted into every one of my data-access components. Now that I know it's a pattern than I use everywhere, I wanted to see if I could factor it out into something more globally-defined.

Following the naming convention for other core ColdFusion data types, I've crated a maybeNew() and a maybeSet() function that codify the pattern:

<cfscript>

	/**
	* I create a maybe with the given value.
	*/
	private struct function maybeNew( any value ) {

		return maybePatchMemberMethods({
			exists: ! isNull( value ),
			value: arguments?.value
		});

	}

	/**
	* I update the maybe to be an exists using the given input.
	*/
	private struct function maybeSet(
		required struct maybe,
		required any value
		) {

		maybe.exists = true;
		maybe.value = value;

		return maybe;

	}

	/**
	* INTERNAL: I patch-in the member methods for the given maybe.
	*/
	private struct function maybePatchMemberMethods( required any maybe ) {

		maybe.set = ( newValue ) => maybeSet( maybe, newValue );

		return maybe;

	}

</cfscript>

All these methods do is attempt to encapsulate and codify the logic that I was copy-pasting into all my ColdFusion components. Now, I can use the maybeNew() method anytime I need to create a "maybe":

<cfscript>

	// Include the core maybe methods.
	include "./maybe-core.cfm";

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

	maybe = maybeNew();

	if ( ! maybe.exists ) {

		maybe.set( "This is the value" );

	}

	writeDump(
		var = maybe,
		showUDFs = false
	);

</cfscript>

In this case, I'm calling maybeNew() without a value; and so, it creates a "not exists" maybe. As such, the if condition is true and I can manually set the value. Running this ColdFusion code gives me the following:

  • exists: true
  • value: This is the value

We could then continue to evolve this concept by making other data-oriented maybe methods. For example, in addition to arrayFirst(), we could have a maybeArrayFirst() which wraps the array access in a "maybe":

<cfscript>

	// Include the core maybe methods.
	include "./maybe-core.cfm";

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

	results = [ /* no results */ ];

	writeDump(
		var = maybeArrayFirst( results ),
		showUDFs = false
	);

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

	/**
	* I return a maybe for the first element in the given array.
	*/
	private struct function maybeArrayFirst( required any collection ) {

		return collection.isDefined( 1 )
			? maybeNew( collection.first() )
			: maybeNew()
		;

	}

</cfscript>

In this example, I'm attempting to get the first element in an empty array. And so, calling this ColdFusion code gives me the following:

  • exists: false
  • value: undefined

This morning, I applied this update to Big Sexy Poems. And already the code is feeling easier to read. Plus, I love the idea that I now have a formal approach to gathering optional data that may or may not be present. For example, when I gather the data for the "View Poem" page, I also look up the "Collection" in which the poem has been categorized:

<cfscript>

	private struct function getPartial(
		required struct authContext,
		required numeric poemID
		) {

		var context = poemAccess.getContext( authContext, poemID, "canView" );
		var poem = context.poem;

		// I don't know if a "collection" is going to be present; so, let's
		// default to an empty "maybe" and then populate as needed.
		var maybeCollection = maybeNew();

		if ( poem.collectionID ) {

			maybeCollection.set( collectionModel.get( poem.collectionID ) );

		}

		return {
			poem,
			maybeCollection,
		};

	}

</cfscript>

When I gather the data for the page, I'm always defining and return the maybeCollection structure. But, I only populate this structure if the poem has a collectionID value. Then, on the CFML-view side, I can easily check this before I output the name and link:

<cfoutput>

	<h1>
		#e( poem.name )#
	</h1>

	<cfif maybeCollection.exists>
		<p>
			<strong>Collection:</strong>
			<a href="....">#e( maybeCollection.value.name )#</a>
		</p>
	</cfif>

</cfoutput>

I just really like the way this reads!

I'm gonna let this "soak" in the application for a while and see how it feels. But so far, I'm loving it.

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

Reader Comments

16,109 Comments

@John,

Exactly, only with muuuuch less ceremony :P I've tried to look at those Java docs before and it always feels like it's trying to solve a much bigger problem-set than I need. I really just want to know if a value is safe to reference. I think the Java/CB one is more geared towards Functional Programming, maybe (I'm speaking above my skills now).

18 Comments

@Ben Nadel,

I just don't really like returning structs from things, they start off all nice and simple but then devs over time decide to add extra data to it and soon it's a mess. That's generally why I prefer to return an object. It could do exactly what you have right now, but reading the code I immediately know what I'm getting (a MayBe instance) rather than 'struct' which is vague.

In it's simplest form your struct could become something like (pseudo code):

/**
 * MayBe.cfc
 **/
component accessors=true {

  property name="value" type="any";

  MayBe function init( any value ) {
    if ( arguments.keyExists( "value" ) ) {
      variables.exists = arguments.exists;
    }
    return this;
  }

  boolean function exists() {
    return !isNull( variables.value );
  }

}

Just throwing ideas out there :)

18 Comments

Calling it becomes

	public Maybe function maybeGet( required numeric id ) {
		var results = gateway.getByFilter( id = id );
		if ( results.len() ) {
			return new Maybe( results.first() );
		}
		return new Maybe();
	}

:D

16,109 Comments

@John,

Definitely part of me is still having PTSD from CFMX 6 when CFC instantiation was dramatically more expensive. I've never really adjusted my mental model for creating transient CFCs. Still to this day, all of my CFCs are just cached in memory at application-boot.

I need to run some speed tests to ease my mind vis-a-vis cost of CFC creation on a per-request basis.

1 Comments

Rust solves this with the Option-Enum which has two variants: Some(value) and None.
Similarly the Result-Enum is used for handling errors and also has two variants: Ok(value) and Err(errorValue).
I wish ColdFusion had Enums.

On the other hand, returning an Option (or Maybe) makes it kind of unclear which type the variable has that is contained. Unfortunately CF also doesn't allow annotations like public Maybe<String> function getNickName() {}, which would be cool. We could also defined typed arrays (e.g. function foo(array<numeric> values) {}).

I do love ColdFusions loose type system for getting stuff done quickly, however sometimes in applications with multiple developers involved, I wished for an stricted - and ofc still optional - type system.

16,109 Comments

@Boris,

For me, this is kind of the point thought:

On the other hand, returning an Option (or Maybe) makes it kind of unclear which type the variable has that is contained.

Meaning, you would only use a Maybe if the state of the value was unclear. For data that should always exist, I just return / consume the data; or, throw an error if the reference is failed (ie, maybe there's dirty data in the database or the URL parameters have been maliciously changed). The Maybe, for me, means that the consumer has to check to see what kind of value it is because it might be different values (exists vs. non-exists).

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