Hello,
This is a good question.
flatMap(traversal) came late in the game and local(traversal) already existed. There is a slight variation in behavior that is subtle
gremlin> g.V().both().barrier().flatMap(both().groupCount("m")).cap("m")
==>[v[1]:3, v[2]:1, v[3]:3, v[4]:3, v[5]:1, v[6]:1]
gremlin> g.V().both().barrier().local(both().groupCount("m")).cap("m")
==>[v[1]:7, v[2]:3, v[3]:7, v[4]:7, v[5]:3, v[6]:3]
TraversalFlatMapStep will project all incoming traversers to a split() with bulk 1. LocalStep will simply pass the traverser through to its wrapped traversal untouched.
It is a subtle difference that should not exist. I think we should really just get rid of LocalStep, but keep local() as I like talking at that higher like. Similar to how where(traversal) is identical to filter(traversal) when the child traversal does not have as() starts/ends. I just like saying “where” vs. “filter."
The reason why we do projection to a split() with bulk 1 is for situations like this:
gremlin> g.V().both().barrier().groupCount().by(outE().count())
gremlin> g.V().both().barrier().flatMap(outE().count()).groupCount()
gremlin> g.V().both().barrier().local(outE().count()).groupCount()
==>[0:3, 1:1, 6:1, 9:1]
See how local() is not really what you want. You don’t want to “double count” edges. This is why all by()-modulators project to a split() w/ bulk 1. LocalStep just isn’t like that… bad, good? …. I never ran into a reason why I would not want that projection save…. :/
Marko.