Velocitip: Use ArrayList.equals() for multiple comparisons

Velocity is the only language where Brevity for Brevity's Sake is OK.

In other languages, reducing total lines of code (LOC) with show-offy shortcuts just means your code will make no sense (to you or anyone else) after a few months away.[1]

But in Velocity, the shortest possible implementation is a good call, since:

  • VTL is verbose (only one major operation permitted per line, temp variables required for void methods)
  • it offers limited reusability (especially Marketo's Velocity setup, where #macro and reused $references don't work consistently)
  • it's completely whitespace-preserving (indenting would help readability, but messes up output — so the fewer lines, the less to mess up)
  • the more lines of code in your template, the more distraction from the “meat,” which is the email content

Imagine you have a few Lead fields using the JETDS naming system:[2] installationEnvironment, typeOfEquipment, and purpose.

There are 10-20 options for each of these fields, so the number of total combinations is huge. Even if you're only interested in a subset, the conditions get loooooong:

#* 
I'm being generous with whitespace here, 
but it's *still* hard to read/maintain! 
VTL is whitespace-preserving, so indents are true spaces.
*#
#if( $lead.installationEnvironment == "amphibious" )
 #if( $lead.typeOfEquipment == "radar" )
   #if( $lead.purpose == "receiving" )
   do something...
   #elseif( $lead.purpose == "detecting" )
   do something else...
   #elseif( $lead.purpose == "navigation-aid" )
   do something else entirely...
   #end
 #elseif ( $lead.typeOfEquipment == "sonar" )
   #if( $lead.purpose == "receiving" )
   do yet another thing...
   #elseif( $lead.purpose == "detecting" )
   do still another thing...
   #elseif( $lead.purpose == "navigation-aid" )
   ...
   #end
 #end
#elseif( $lead.installationEnvironment == "ground-mobile" )
 #if( $lead.typeOfEquipment == "radar" )
   #if( $lead.purpose == "receiving" )
   ...
   #elseif( $lead.purpose == "detecting" )
   ...
   #elseif( $lead.purpose == "navigation-aid" )
   ...
   #end
 #end
#end
## etc., etc.

Or you might build out the combos like:

#if( 
  $lead.installationEnvironment == "amphibious" && 
  $lead.typeOfEquipment == "radar" &&
  $lead.purpose == "receiving"
)
do something...
#elseif( 
  $lead.installationEnvironment == "amphibious" && 
  $lead.typeOfEquipment == "radar" &&
  $lead.purpose == "detecting"
)
do something else...
## etc., etc.

But this isn't any better for brevity, and is still gonna make your code an error-prone mass.

The better way

Check this out:

#set( $leadProductInfo = [
  $lead.installationEnvironment,
  $lead.typeOfEquipment,
  $lead.purpose
] )
#if( $leadProductInfo.equals(["amphibious","radar","receiving"]) )
do something...
#elseif( $leadProductInfo.equals(["amphibious","radar","detecting"]) )
do something else...
#elseif( $leadProductInfo.equals(["amphibious","radar","navigation-aid"]) )
do something else entirely...
#elseif( $leadProductInfo.equals(["amphibious","sonar","receiving"]) )
do yet another thing...
## etc., etc.

Much more concise, isn't it? You only need one line per triple-comparison, and you don't need to repeat your field names over and over, just the strings you're looking for. Less to type, less to mess up.

How does it work?

This magic is thanks to Java's AbstractList.equals method, which is inherited by Java ArrayLists, and in turn by Velocity-wrapped arrays, which are ArrayLists under the hood.

Here's how equals works, from the docs:

Returns true if and only if the specified object is also a list, both lists have the same size, and all corresponding pairs of elements in the two lists are equal. (Two elements e1 and e2 are equal if (e1==null ? e2==null : e1.equals(e2).) In other words, two lists are defined to be equal if they contain the same elements in the same order.

While confidently written, that explanation still might not be clear to newish programmers, because it relies on a specific definition of “the same.” So read on.

equals() is not == !

If you're unfamiliar with how objects work in Java (as in all OO languages), you might read the equals doc above and try this:

#if( $leadProductInfo == ["amphibious","radar","receiving"] )

Nope, that won't work the same way at all.

See, the “equality” operator == isn't the same as the equals method. And, to be frank, the identical names really suck and are bound to lead to confusion.

The equals method would be better named one of these:

  • equivalentTo (since it's not true equality but practical equivalence that you're checking, per your business rules)
  • intelligentlyEqualTo

And the == would be better called:

  • reference equality
  • identity

When comparing two ArrayLists, == will never return true because they're different objects. It doesn't matter if they have the same items in the same index order: if they're different arrays at the outer level, they're never ==.

But they can still be equals, because that special method is defined as doing an inner comparison (inside the array) on the array items, not the array itself.

And multidimensional arrays will continue to use equals, so this:

[1,2,[3,4],"pumpkin"].equals([1,2,[3,4],"pumpkin"])

is also true.

If you're just coming to terms with programming concepts, this may seem like a Duh! moment since you guessed it worked this way, but again the key is that == does not do this inner comparison.

Other languages: JavaScript

Not every OO language has a built-in concept like equals.

JavaScript, for example, has two types of equality operators, == and ===. (The longer === version fills basically the same role as Java's ==.) Yet JS Arrays don't have an equals method.

You could write your own equals(array1,array2) function, or in the latest browsers and servers create a custom subclass of Array with a custom equals method.[3] — but those are both far afield from Java's equals, which is built into the language.

For the record, here's one way you might implement this in JS (but this quickie will only work for one-dimensional arrays of scalar variables):

function equals(array1,array2){
  return array1.length == array2.length &&
    array1.every(function(that,idx){
      return this[idx] === that;
    }, array2);
}
equals([1,2,3],[1,2,3]); // true
equals([],[]); // true, same length 0, empty array allowed
equals([1,2,3],[1,2,3,4]); // false, different length
equals([1,2,3],[1,"2",3]); // false, fails strict comparison

Other languages: PHP

PHP, in that special way it has of making you bang your head against the wall, uses the === (3 = characters) operator in essentially the same way equals works in Java:

// might be true or false, as it's testing internal equivalence
// i.e. same length & equivalent scalars or arrays at each index
if ( $leadProductInfo === ["amphibious","radar","receiving"] )

If you're following so far, this means when each side is an array, PHP === behaves in the exact opposite way from both Java & JavaScript ===.

To be fair, given the history of PHP this isn't surprising. PHP was a procedural language for may years and the object support was added later, so it's not an “OO language” but a language with a sort of OO-zone where certain features (and most of the latest features) reside.

Arrays are among the oldest PHP features, and as such are not considered objects. So PHP isn't breaking OO principles, though it's mixing old and new things together in a confusing way.

One way to be able to switch between equivalence and identity is to cast your variables between (object) and (array) depending on which comparison you want:

$leadProductInfo = (object)[
  $lead->installationEnvironment,
  $lead->typeOfEquipment,
  $lead->purpose
];
// always false, now it's using object identity
if ( $leadProductInfo === (object)["amphibious","radar","receiving"] )
// might be true or false, using equivalence
if ( (array)$leadProductInfo === ["amphibious","radar","receiving"] )

You could use an ArrayObject instead of (object)-ifying an array, but AFAIK you still need to (array)-ify it back for equivalence comparison. Maybe there's a smoother way, but I've been away from PHP for too long to know for sure.


Notes

[1] Unless you document how your shortcuts work, but that defeats the purpose of showing off.
[2] Chose JETDS 'cuz I memorized it when I was a kid… not that it's fresh right now!
[3] And you also have to make sure all objects you're comparing, at all levels of a multidimensional array, have an equals method or use a default implementation.