My apologies, I seem to have forgotten to link my my footnotes, and additionally, my mail client seems to have converted P.lt to a hyperlink without my noticing. Please disregard the links in the previous examples, and the intended hyperlinks are included below:
[1] TBL Semantics docs: https://tinkerpop.apache.org/docs/3.7.3/dev/provider/#_ternary_boolean_logics [2] Strategy Dependence of TBL: https://issues.apache.org/jira/browse/TINKERPOP-2940 Thanks, Cole On 2025/07/04 21:56:24 Cole Greer wrote: > As of TinkerPop 3.6.0, gremlin has used a ternary boolean logic (TBL) system > to > consistently resolve invalid comparisons (incomparable types, comparisons to > NaN...). This is documented in our semantic docs[1]. Such a system is standard > in many declarative query languages such as SQL and SPARQL, however in > gremlin, it leads to a lot of subtle complexity and unintuitive consequences. > > The primary challenges regarding TBL in gremlin has to do with the notion of > “reducing” ERROR states to FALSE. Ternary boolean logic is typically used in a > query language in expressions which filter results. If there is some > expression > such as WHERE x.name is "Bob" AND x.age > 32 and x = {name: null, age: 21}, > then the expression evaluates to (ERROR AND FALSE) -> ERROR. The result of > this > is that `x` should be filtered from the results as the expression did not > evaluate > to TRUE. In essence, ERROR was reduced to FALSE after the logic expression was > fully evaluated, right before the filter was applied. The main challenge in > gremlin > is determining what is the boundary of a logic expression and when should an > ERROR state be reduced. The “reduction point” in most declarative query > languages is obvious as these filtering expressions are typically contained > in some > syntactically well-defined where-clause. Gremlin’s functional syntax results > in > complex filtering expressions being weaved throughout a query, with no clear > boundaries. > > The current gremlin semantics are defined such that if any FilterStep > receives a > GremlinTypeErrorException (indicating an invalid comparison took place), it > will > propagate the error to its parent step if the parent is also a FilterStep. > Otherwise, > it will drop the current traverser (effectively reducing the ERROR to a > FALSE). The > only exception is the and() and or() steps which will handle the ERROR > according > to TBL semantics. > > This reduction behaviour is very unintuitive for users and leads to many > seemingly > equivalent queries producing different results, as well as certain > optimization > strategies unexpectedly altering traversal semantics [2]. Some examples of > this are: > > gremlin> > g.withoutStrategies(InlineFilterStrategy).inject(5).not(is(P.lt<http://p.lt/>(Double.NaN))) > // No results > gremlin> > g.withoutStrategies(InlineFilterStrategy).inject(5).not(where(is(P.lt<http://p.lt/>(Double.NaN)))) > ==>5 > > gremlin> g.inject(5).not(or(is(P.lt<http://p.lt/>(Double.NaN)), is(3))) > // No results > // Error propagates through or() to not(), after which it is reduced to false > and > // results are filtered out > gremlin> g.inject(5).not(coalesce(is(P.lt<http://p.lt/>(Double.NaN)), is(3))) > ==>5 > // Coalesce is not a filter step, so it forces is() to reduce the ERROR to > false > // early, instead of passing the ERROR all the way to not() > > gremlin> > g.withoutStrategies(InlineFilterStrategy).inject(1).not(where(is(lt(NaN)))) > ==>1 > gremlin> g.inject(1).not(where(is(lt(NaN)))) > // No results > // InlineFilterStrategy optimizes to simply not(is(lt(NaN))) which removes the > // early reduction from the where() step > > I believe these types of issues with TBL are inevitable in gremlin due to the > lack of a > clearly defined where-clause which encapsulates all filtering. For this > reason, I > propose that we replace the ternary boolean semantics with simpler binary > boolean > semantics which do not face this reduction problem. If adopted, that would > mean > that invalid comparisons such as 5>“foo” or 1.0<NaN would immediately > return FALSE, and thus not(5>“foo”) would evaluate to TRUE. This is different > from > our current semantics, but it is both consistent and communicable, which are > both > are very challenging to achieve if we retain the ternary semantics. > > Please let me know any thoughts you have on the proposed semantics change. If > there are no objections, I intend to update the implementation and > documentation to follow binary boolean semantics in 3.8-dev. > > Thanks, > Cole >