Getting good at Gremlin is as much about writing readable and intutive traversals as it is writing performant ones. Gremlin has a lot of ways to accomplish the same goal and you can easily write two equivalent traversals with a wildly different set of steps. It is also easy to write a working query that uses steps in ways that do not easily convey meaning and intent to someone who comes along and reads it later - and that person may be yourself a year or more down the line. As such, I’m a big proponent of Gremlin readability and often question if the proper steps and patterns are being used even if my traversal is working
Here’s a simple example:
The above example shows that we can use
coalesce() to set a property of “k” if it does not exist. As a reminder,
coalesce() will return the result of first child traversal argument that returns a value. Therefore, if
has('k') succeeds as a filter in finding the “k” property key present, then the same vertex that entered the
coalesce() is the same that will exit. If the “k” property key is not present then the property is added as a side-effect and therefore the same vertex that entered the
coalesce() will also exit it.
This traversal succeeds in its goal, but it makes a bit of an unexpected use of
coalesce() in my mind. The
coalesce() step is actually a
flatMap() step and therefore implies something transformative happening to the traverser, but in this usage the traverser carrying the vertex that enters the
coalesce() passes through unchanged (ignoring the side-effect of
property('k','v')) irrespective of the path taken. It takes a moment to realize that
coalesce() used in this fashion is really just an if-then pattern. To compound the matter, the
if portion has to be inverted for it to work in the
coalesce() context. In other words, we really want to say “if the ‘k’ property is not present, then …”, but we’re instead forced to read the reverse of
has('k') so that a vertex that has it will pass through unchanged.
Since we have an if-then pattern, then perhaps it would be more explicit and readable to present it that way with
choose() which is designed for that purpose:
The above example makes the if-then far more explicit and we can more immediately recognize that section of Gremlin as “if there is no ‘k’ property then it should be added”. While the traversal readability has improved, it could still be modified to convey more about the intent. The primary purpose of the if-then pattern is to trigger a side-effect of adding a property key, which is really easy to read as a single traversal of
hasNot('k').property('k','v'). It’s nice to be able to immediately know that a particular traversal is meant purely for side-effect purposes as your mind can block out the entire section of a traversal, no matter how complex it is, knowing that it will not alter the current traverser:
The above example demonstrates two ways to do this isolation. The first is to simply use
sideEffect() because it explicitly denotes the child traversal it contains as being executed solely for that purpose. The second approach, which uses
optional(), is a bit more specific to this case, where the child traversal is extremely simplistic and it’s clear that the incoming traverser will not be transformed. I think
optional() has a readability benefit here over
coalesce(), despite them being similar steps, because
optional() doesn’t force the check for “k” to be inverted and the entire child traversal stays together as opposed to being separate arguments.
Please bear in mind that these traversals might all look quite simple and equally readable, but when expanded into the real world with traversals that are dozens of lines long, these patterns tend to matter. It’s important to be able to immediately spot an if-then situation or a long child traversal that just trickles into a side-effect. Without those signals, deciphering Gremlin can end up quite time consuming.