You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2022/02/01 15:17:11 UTC

[tinkerpop] branch master updated: Gremlin Value Expressions 2.0 / Ternary Boolean Logics

This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


The following commit(s) were added to refs/heads/master by this push:
     new af82ce1  Gremlin Value Expressions 2.0 / Ternary Boolean Logics
     new 47d4115  Merge pull request #1553 from mikepersonick/TINKERPOP-2687
af82ce1 is described below

commit af82ce196453a7c26b7804b9ee5ed6efd33f289b
Author: Mike Personick <mi...@supersonick.io>
AuthorDate: Thu Jan 20 08:53:28 2022 -0700

    Gremlin Value Expressions 2.0 / Ternary Boolean Logics
    
      https://issues.apache.org/jira/browse/TINKERPOP-2687
---
 CHANGELOG.asciidoc                                 |   7 +-
 docs/src/dev/developer/for-committers.asciidoc     |   1 -
 docs/src/dev/provider/gremlin-semantics.asciidoc   | 301 +++++++++++--------
 docs/src/upgrade/release-3.6.x.asciidoc            |  21 +-
 .../gremlin/process/traversal/Compare.java         |  64 +---
 .../gremlin/process/traversal/Contains.java        |  16 +-
 .../OrStep.java => GremlinTypeErrorException.java} |  32 +-
 .../tinkerpop/gremlin/process/traversal/Order.java |  24 +-
 .../tinkerpop/gremlin/process/traversal/Text.java  |   9 +
 .../process/traversal/step/filter/AndStep.java     |  13 +-
 .../{OrStep.java => BinaryReductionStep.java}      |  25 +-
 .../process/traversal/step/filter/FilterStep.java  |  21 +-
 .../process/traversal/step/filter/OrStep.java      |  13 +-
 .../traversal/step/filter/TraversalFilterStep.java |   2 +-
 .../traversal/step/filter/WhereTraversalStep.java  |   2 +-
 .../traversal/translator/DotNetTranslator.java     |   4 +
 .../traversal/translator/GroovyTranslator.java     |   4 +-
 .../gremlin/process/traversal/util/AndP.java       |  14 +-
 .../gremlin/process/traversal/util/OrP.java        |  14 +-
 .../gremlin/util/GremlinValueComparator.java       | 330 +++++++++++++++++++++
 .../tinkerpop/gremlin/util/NumberHelper.java       | 104 +++++--
 .../gremlin/util/OrderabilityComparator.java       | 189 ------------
 .../gremlin/util/tools/CollectionFactory.java      |  52 ++++
 .../tinkerpop/gremlin/util/tools/MultiMap.java     |   1 -
 .../process/traversal/CompareExceptionTest.java    |  76 -----
 .../gremlin/process/traversal/CompareTest.java     | 195 ++++++++++--
 .../gremlin/process/traversal/ConnectiveTest.java  | 161 ++++++++++
 .../gremlin/process/traversal/ContainsTest.java    |  17 +-
 .../gremlin/process/traversal/OrderTest.java       | 180 ++++++++---
 .../tinkerpop/gremlin/process/traversal/PTest.java |  24 +-
 .../features/semantics/Orderability.feature        |   4 +-
 .../process/AbstractGremlinProcessTest.java        |   7 +
 .../process/ProcessLimitedStandardSuite.java       |  57 +---
 .../gremlin/process/ProcessStandardSuite.java      |   2 +
 .../process/traversal/step/OrderabilityTest.java   |  18 +-
 .../traversal/step/TernaryBooleanLogicsTest.java   | 321 ++++++++++++++++++++
 .../traversal/step/sideEffect/TinkerGraphStep.java |  19 +-
 37 files changed, 1667 insertions(+), 677 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index a6fde1c..5bec61b 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -23,7 +23,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 [[release-3-6-0]]
 === TinkerPop 3.6.0 (Release Date: NOT OFFICIALLY RELEASED YET)
 
-* Implemented total orderability across types per the Gremlin semantics for comparability/orderability defined in the Graph Provider documentation.
+* Implemented comparability/orderability semantics defined in the Graph Provider documentation.
 * Changed TinkerGraph to allow identifiers to be heterogeneous when filtering.
 * Prevented values of `T` to `property()` from being `null`.
 * Added `fail()` step.
@@ -57,6 +57,11 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Fixed a potential connection load balancing issue due to a race condition not updating the usage count.
 * Extended `property()`` to allow for setting a `Map` of property values.
 
+==== Improvements
+
+* TINKERPOP-2687 Boolean Value Expressions 2.0 (Comparability without errors)
+* TINKERPOP-2641 Allow orderability on any type and across types (Orderability without errors)
+
 == TinkerPop 3.5.0 (The Sleeping Gremlin: No. 18 Entr'acte Symphonique)
 
 image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/images/gremlin-sleeping-beauty.png[width=185]
diff --git a/docs/src/dev/developer/for-committers.asciidoc b/docs/src/dev/developer/for-committers.asciidoc
index 20486b0..7b01b18 100644
--- a/docs/src/dev/developer/for-committers.asciidoc
+++ b/docs/src/dev/developer/for-committers.asciidoc
@@ -502,7 +502,6 @@ are two types of tags:
 * `@StepClass*` - Marks the step grouping and is a prefix that precedes and either refers to one of the following:
 ** One of the four types of steps: `Branch`, `Filter`, `Map`, and `SideEffect` (e.g. `@StepClassBranch`)
 ** `Semantics` which maps to elements of the link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#gremlin-semantics[Gremlin Semantics] specification.
-** `Orderability` which maps to the total orderability semantics defined in the link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#gremlin-semantics[Gremlin Semantics] specification.
 ** An `Integrated` grouping that does not fit those individual classifications well.
 * `@Step*` - Marks testing for a particular step. While this tag is generally unique to the feature
 file itself and test filtering could be accomplished at that level by way of the file, the use of the tag is a
diff --git a/docs/src/dev/provider/gremlin-semantics.asciidoc b/docs/src/dev/provider/gremlin-semantics.asciidoc
index 773714d..3bb1244 100644
--- a/docs/src/dev/provider/gremlin-semantics.asciidoc
+++ b/docs/src/dev/provider/gremlin-semantics.asciidoc
@@ -51,7 +51,7 @@ The TinkerPop query execution runtime handles the following primitive types:
 * UUID
 * Date
 * `nulltype`
-  ** It denotes the "undefined" value.
+  ** Has only one value in its type space - the "undefined" value `null`
 
 NOTE: TinkerPop has a bit of a JVM-centric view of types as it was developed within that ecosystem.
 
@@ -115,7 +115,7 @@ equivalence defines value collation semantics in the context of, for instance, d
 equivalence over two values `a := Double.NaN` and `b:= Double.NaN` is true, but equality would (in our proposal) be
 defined as false; the rational here (which is commonly found in query and programming languages) is that comparing two
 "unknown" numbers — which is a frequent use case for NaN, cannot certainly be identified as equal in comparison, but it
-typically makes sense to group them together in, for instance, aggregations.
+typically makes sense to group them together in, for instance, aggregations and order by.
 
 Both equality and equivalence can be understood as complete, i.e. the result of equality and equivalence checks is
 always either `true` or `false` (in particular, it never returns nulltype` or throws an exception). The details on
@@ -281,7 +281,7 @@ Equivalence is identical to Equality, except for the cases listed below.
 ** -INF is equivalent to -INF
 ** +INF is equivalent to +INF
 ** They are equialavlent to each other irrespective to its underlying type, so in Java, for example, Double.POSITIVE_INFINITY is equivalent to Float.POSITIVE_INFINITY.
-* NaN is not equivalent to any other numbers
+* NaN is not equivalent to any (non-NaN) numbers
 ** NaN *is equivalent to* NaN irrespective to its underlying type, so in Java, for example, Double.NaN is equivalent to Float.NaN.
 
 ===== `nulltype`
@@ -291,84 +291,130 @@ Equivalence is identical to Equality, except for the cases listed below.
 
 == Comparability vs. Orderability
 
-Comparability and orderability can be understood as the "dual" concepts of equality and equivalence for range
-comparisons (rather than exact comparison). For the two values of the same type (except for NaN), comparability is
-stronger than orderability in the sense that everything that every order between two values that holds `true` w.r.t.
-comparability also holds `true` w.r.t. orderability, but not vice versa. Comparability is what is being used in range
-predicates. It is restricted to comparison within the same type or, for numerics, class of types; comparability is
-complete within a given type, but returns `nulltype` if the two types are considered incomparable (e.g., an integer
-cannot be compared to a string). Orderability fills these gaps, by providing a stable sort order over mixed type
-results; it is consistent with comparability within a type, and complete both within and across types, i.e. it will
-never return `nulltype` or throw an exception.
+Comparability and orderability can be understood as analagous concepts to equality and equivalence but for range
+comparisons (rather than exact comparison). For any two values within the same type space (except for NaN),
+comparability and orderability are the same. The key differences between comparability and orderability are as follows:
 
-More details on comparability and orderability are sketched in the following two subsections, respectively.
+|===
+|Scenario|Comparability|Orderability
+
+|NaN|Not comparable to anything, including itself|Appears after +Infinity in the numeric type space
+|Two values of different types|Not comparable. This includes the `nulltype`|Subject to a total type ordering where every value of type A appears before
+or after every value of Type B depending on the types
+|===
+
+Comparability is what is being used in range predicates. It is restricted to comparison within the same type or,
+for numerics, class of types; comparability is complete within a given type, but returns `ERROR` if the two types are
+considered incomparable (e.g., an integer cannot be compared to a string).
+
+`ERROR` represents an extension of normal boolean logics - it is a third option value in a ternary
+boolean logics system for boolean value expression evaluation. `ERROR` is an internal representation only and will not
+be propagated back to the client as an exception - it will eventually hit a binary reduction operation and be reduced
+to `false`. Before that happens though, it will be treated as its own boolean value that can be used in other boolean
+value expressions, such as `AND`/`OR`:
+
+|===
+|A|B|=>|AND|OR|XOR
+
+|TRUE | TRUE |=>| TRUE | TRUE | FALSE
+|TRUE | FALSE |=>| FALSE | TRUE | TRUE
+|TRUE | ERROR |=>| ERROR | TRUE | ERROR
+|FALSE | TRUE |=>| FALSE | TRUE | TRUE
+|FALSE | FALSE |=>| FALSE | FALSE | FALSE
+|FALSE | ERROR |=>| FALSE | ERROR | ERROR
+|ERROR | TRUE |=>| ERROR | TRUE | ERROR
+|ERROR | FALSE |=>| FALSE | ERROR | ERROR
+|ERROR | ERROR |=>| ERROR | ERROR | ERROR
+|===
+
+The NOT predicate inverts TRUE and FALSE, respectively, but maintains ERROR values. The key idea is that, for an
+ERROR value, we can neither prove nor disprove the expression, and hence stick with ERROR.
+
+|===
+|arg|=>|result
+
+|TRUE |=>| FALSE
+|FALSE |=>| TRUE
+|ERROR |=>| ERROR
+|===
+
+Common `ERROR` cases are comparisons against NaN, cross-type comparisons, or invalid arguments to other boolean value
+expressions.
+
+Orderability provides a stable sort order over mixed type results without errors or exceptions; it is consistent with
+comparability within a type, and complete both within and across types, i.e. it will never return `ERROR` or throw
+an exception.
+
+More details on comparability and orderability are described in the following two subsections, respectively.
 
 === Comparability
 
-* Used by the comparison operators (`P.gt`, `P.lt`, `P.gte`, `P.lte`) in Gremlin and defines how to compare two values. +
+Comparability is used by the compare operators (`P.gt`, `P.lt`, `P.gte`, `P.lte`) in Gremlin and defines how to
+compare two values.
+
 Example:
 
 [code]
 ----
 // comparison over two property values
-gremlin> g.E().has("weight", gt(1))
+gremlin> g.E().has("weight", P.gt(1))
 ----
 
-* For numbers,
-** it should be aligned to equality conceptually as far as type promotion is concerned. e.g. `1.0 < 2 < 3L`
-* Comparison should not result in undefined behavior, but can return `nulltype` if and only if we are comparing
-incomparable data types. How this `nulltype` result is handled is Graph provider dependent.
-* Otherwise Comparison does return `true` or `false`
+Comparison should generally return true or false and not result in undefined behavior, but can return `ERROR` in certain
+cases (comparison across data types, comparing against `NaN`, invalid arguments to other boolean value
+expressions). How this `ERROR` result is handled is Graph provider dependent. The reference implementation does a final
+binary reduction from `ERROR` to `FALSE` (and thus quietly filters the solution that produced the `ERROR`).
 
 ==== Primitive types
 
 ===== Number
 
-* If either one of LHS or RHS is Numbers and another isn’t, throw an Exception. This comes first before the handling for each type.
+* For numbers, comparison should be aligned to equality conceptually as far as type promotion is concerned.
+e.g. `1.0 < 2 < 3L`
+* If one argument a Number and the other isn’t, return `ERROR` for all comparisons. This comes first before the handling
+for each numeric type.
 * If both LHS and RHS are Numbers, try the type casting, and then compare two values.
 * For -0.0, 0.0, +0.0, lt and gt returns `false` and lte, gte returns `true` because they are "equal" in this semantics.
 * -INF < +INF
-* Any comparison between NaN and any numbers (including NaN) should return `false` +
-https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.2.3
-* IF `nulltype` and NaN is compared it should return `nulltype` as their "type" is different and they are not comparable.
+* Any comparison between NaN and any Number (including NaN itself) returns `ERROR` (which is reduced to `false`) +
+https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.2.3 +
 
 ===== Boolean
 
-* If either one of LHS or RHS is Boolean and another isn’t, throws an Exception
+* If one argument is Boolean and the other isn’t, returns `ERROR`.
 * False < True
 
 ===== String
 
-* If either one of LHS or RHS is String and another isn’t, returns `nulltype`.
+* If one argument is String and the other isn’t, returns `ERROR`.
 * We assume the common lexicographical order over unicode strings
 * LHS and RHS are compared lexicographically
-* UUID is evaluated based on its String representation.
 
 ===== UUID
 
+* If one argument is UUID and the other isn’t, returns `ERROR`.
 * UUID is evaluated based on its `String` representation.
-* However, for example, UUID("b46d37e9-755c-477e-9ab6-44aabea51d50") and String "b46d37e9-755c-477e-9ab6-44aabea51d50" cannot be compared with each other, hence comparing them returns `nulltype`.
+* However, for example, UUID("b46d37e9-755c-477e-9ab6-44aabea51d50") and String "b46d37e9-755c-477e-9ab6-44aabea51d50"
+cannot be compared with each other, hence comparing them returns `ERROR`.
 
 ===== Date
 
-* If either one of LHS or RHS is Date and another isn’t, throw an Exception
+* If one argument is Date and the other isn’t, returns `ERROR`.
 * Compare LHS and RHS based on chronological order, i.e. numerical order in timestamp.
 
-===== `nulltype`
-
-* `nulltype` is not comparable, if the LHS or RHS is `nulltype` then the comparison result is `nulltype`.
-
 ==== Composite types
 
-For all of them, if LHS and RHS is not of the same data type, equality returns `false`. The following semantics applied when both LHS and RHS has the data type.
+For all of them, if LHS and RHS is not of the same type, compare returns `ERROR`. The following semantics applied
+when both LHS and RHS have the same type:
 
 ===== Vertex / Edge / VertexProperty
 
-They are not comparable, return `nulltype`.
+They are compared by their ID. The ID is chosen internally by the implementation, so comparison is implementation
+specific.
 
 ===== Property
 
-It it not comparable, return `nulltype`.
+Compared first by property key, then by property value according to comparability semantics.
 
 ===== PropertyKey
 
@@ -386,158 +432,161 @@ IDs are of any primitive types defined, so comparability for a corresponding typ
 
 Comparability of String applies.
 
+===== Iterable (Path, List, Set, Map)
+
+* Iterables must be of the same type, otherwise `ERROR` (e.g. List and Set are incomparable).
+* Comparison of iterables without inherent order (Set and Map) are implementation-specific.
+** For the reference implementation, all iterables are compared via their natural iteration order.
+* Elements are compared element by element from the two iterations according to conmparability semantics.
+* Empty iterations are equal and are less than non-empty iterations.
+* If iteration `A` exhausts its elements before iteration `B` then `A < B`.
+
 ===== Path
 
-It is not comparable, throw an Exception.
+* Only comparable to other Paths, otherwise `ERROR`.
+* Iterable semantics apply.
+
+===== Set
+
+* Only comparable to other Sets, otherwise `ERROR`.
+* Iterable semantics apply.
 
 ===== List
 
-It is not comparable, throw an Exception.
+* Only comparable to other Lists, otherwise `ERROR`.
+* Iterable semantics apply.
 
 ===== Map
 
-It is not comparable, throw an Exception.
-
-===== Map.Entry
+* Only comparable to other Maps, otherwise `ERROR`.
+* Iterable semantics apply (using `Map.entrySet()`)
+* Map entries are compared first by key, then by value according to comparability semantics.
 
-It is not comparable, throw an Exception.
+=== Mapping for P
 
-===== Set
+The following table maps the notions proposed above to the various `P` operators:
 
-It is not comparable, throw an Exception.
+[%header]
+|================
+|Predicate|Concept
+|P.eq     |Equality
+|P.neq    |Equality
+|P.within |Equality
+|P.without|Equality
+|P.lt     |Comparability
+|P.gt     |Comparability
+|P.lte    |Equality, Comparability
+|P.gte    |Equality, Comparability
+|P.inside |Comparability
+|P.outside|Comparability
+|P.between|Equality, Comparability
+|================
 
 === Orderability
 
-* Used to determine the order. In TinkerPop, the order step follows the notion of orderability.
-* Orderability must not result in `nulltype` / undefined behavior.
-* Orderability must not throw an error. In other words, even if two values are incomparable we should still be able to
-determine the order of those two. This inevitably leads to the requirement to define the order across different data
-types. For the detailed order across types, see appendix.
-* Orderability determines if two values are ordered at the same position or one value is positioned earlier than another.
-* The concept of equivalence is used to determine if the two values are at the same position
-* When the position is identical, which value comes first (in other words, whether it should perform stable sort)
-depends on graph providers' implementation.
-* For values of the same type, comparability can be used to determine which comes first except for `NaN` in Number.
-For a different type, we have a dedicated order as described in the section below.
-
-To sort across any types of values, we define the order between each type as follows:
-(In this order, ID, label, property key and property value are considered as a part of primitive types)
+Orderability semantics are used to define a global order across all Gremlin values. In the reference implementation, any
+step using `Order.asc` or `Order.desc` (e.g. OrderGlobalStep, OrderLocalStep) will follow these semantics. Unlike
+Comparability, Orderability will not result in `ERROR` for any comparisons - even if two values are incomparable we will
+still be able to determine their respective order. We do this by defining a global order across type, and values in
+different type spaces will be ordered according to their priority (e.g. all Numbers < all Strings).
 
-* `nulltype`
-* Boolean
-* Number
-* Date
-* String
-* Vertex
-* Edge
-* VertexProperty
-* Property
-* Path
-* List
-* Map
+We define the type space, and the global order across the type space as follows:
+----
+1.  nulltype
+2.  Boolean
+3.  Number
+4.  Date
+5.  String
+6.  Vertex
+7.  Edge
+8.  VertexProperty
+9.  Property
+10. Path
+11. Set
+12. List
+13. Map
+14. Unknown
+----
+
+Within a given type space, orderability determines if two values are ordered at the same position or one value is
+positioned before or after the another. When the position is identical, which value comes first (in other words,
+whether it should perform stable sort) depends on graph providers' implementation. For almost all cases, comparability
+can be used to determine orderability within a type space, with a few exceptions outlined below.
 
 ==== Primitive types
 
 ===== Number
 
-* Same applies as Comparability. Exceptions are as below:
-** NaN is ordered at a larger index among all Numbers. i.e. after +INF.
-* We do type promotion for orderability as we do for comparability.
+Same as Comparability, except NaN is equivalent to NaN and is greater than all other Numbers, including +Infinity.
+Additionally, because of type promotion (`1` == `1.0`), numbers of the same value but of different numeric types will
+not have a stable sort order.
 
 ===== Boolean
 
-* False < True
+Same as Comparability.
 
 ===== String
 
-* String value is ordered lexicographically
+Same as Comparability.
 
 ===== UUID
 
-* UUID is ordered lexicographically based on its String representation
+Same as Comparability.
 
 ===== Date
 
-* Date value is ordered chronologically
-
-===== `nulltype`
-
-* `nulltype` is before all value types
+Same as Comparability.
 
 ==== Composite types
 
 ===== Vertex / Edge / VertexProperty
 
-They are ordered by their ID. The ID is chosen internally by the implementation, so ordering is implementation specific, but is guaranteed to be stable.
+Same as Comparability.
 
 ===== Property
 
-They are ordered by property key. If the key is equal, then property value is used as the second key.
+Same as Comparability, except orderability semantics are used for the property value.
 
 ===== PropertyKey
 
-Comparability of String applies.
+Same as Comparability.
 
 ===== PropertyValue
 
-Property values are of any primitive types defined, so orderability for a corresponding type applies.
+General orderability semantics apply, since property values can be of any primitive type.
 
 ===== ID
 
-IDs are of any primitive types defined, so orderability for a corresponding type applies.
+General orderability semantics apply, since IDs can be of any primitive type.
 
 ===== Label
 
-Comparability of String applies.
+Same as Comparability.
 
-===== Path
+===== Iterable (Path, List, Set, Map)
 
-* Orderability of the 1st element in the Path applies. Empty Path should come first.
-* If the 1st element is tie, then check the next element, and so on.
-* If one Path exhausts the element fast then it comes earlier in the order.
+Same as Comparability, except orderability semantics apply for element comparisons.
 
-===== List
+===== Path
 
-* Orderability of the 1st element in the List applies.
-* Empty List should come first.
-* If the 1st element is tie, then check the next element, and so on.
-* If one List exhausts the element fast then it comes earlier in the order.
+* Iterable semantics apply.
 
-===== Map
+===== Set
 
-* For two maps, get the 1st entry (a key-value pair) from both, the orderability between them decides the order of the maps.
-* If the 1st entry is tie, then we pick the second one and repeat the process until we determine the order.
-    ** So the orderability of Map depends on in which order they return an entry. It is implementation dependent and undefined in this semantics.
-* If one Map exhausts an entry earlier than another, then it comes earlier in the order.
+* Iterable semantics apply.
 
-===== Map.Entry
+===== List
 
-* First check the orderability of their key.
-* If the key ties, then check the orderability of their value.
+* Iterable semantics apply.
 
-===== Set
+===== Map
 
-* For two sets, get the 1st item from both, the orderability between them decides the order of the sets.
-* If the first item is tie, we pick the second one and so on until we determine the order.
-    ** So the orderability of Set depends on in which order they return an item. It is implementation dependent and undefined in this semantics.
-* If one Set exhausts an item earlier than another, then it comes earlier in the order.
+* Iterable semantics apply for `Map.entrySet()`
+* Map Entries are ordered first by key, then by value according to orderability semantics.
 
-=== Mapping for P
+===== Unknown
 
-The following table maps the notions proposed above to the various `P` operators:
+* If the unknown arguments are of the same type and implement Comparable, their natural order is used.
+* If the unknown arguments are of different types or do not define a natural order, order first by Classname, then
+by `Object.toString()`.
 
-[%header]
-|================
-|Construct|Concept
-|P.eq     |Equality
-|P.neq    |Equality
-|P.within |Equality
-|P.without|Equality
-|P.lt     |Comparability
-|P.gt     |Comparability
-|P.lte    |Equality, Comparability
-|P.gte    |Equality, Comparability
-|P.inside |Comparability
-|P.outside|Comparability
-|P.between|Equality, Comparability
-|================
\ No newline at end of file
diff --git a/docs/src/upgrade/release-3.6.x.asciidoc b/docs/src/upgrade/release-3.6.x.asciidoc
index 6166865..fb07a12 100644
--- a/docs/src/upgrade/release-3.6.x.asciidoc
+++ b/docs/src/upgrade/release-3.6.x.asciidoc
@@ -601,7 +601,23 @@ been removed so providers will need to construct their own exception.
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2507[TINKERPOP-2507]
 
-===== Orderability Semantics
+===== Comparability/Orderability Semantics
+
+Prior to 3.6, comparability was not well defined and produced exceptions in a variety of cases. The 3.6 release
+rationalizes the comparability semantics, defined in the Graph Provider Documentation. One feature of these semantics
+is the introduction of a Ternary Boolean Logics, where `ERROR` cases are well defined, and errors are no longer
+propagated back to the client as an exception. The `ERROR` value is eventually reduced to `false`, which results in
+the solution being quietly filtered and allows the traversal to proceed normally. For example:
+
+[source,text]
+----
+gremlin> g.inject(1, "foo").is(P.gt(0)).count() // 3.5.x
+Cannot compare 'foo' (String) and '0' (Integer) as both need to be an instance of Number or Comparable (and of the same type)
+Type ':help' or ':h' for help.
+
+gremlin> g.inject(1, "foo").is(P.gt(0)).count() // 3.6.0
+==>1
+----
 
 Prior to 3.6, orderability (OrderGlobalStep) only applied to a single typespace and only to certain types. Attempts to
 order across types resulted in an exception. The 3.6 release introduces total orderability semantics, defined in the
@@ -661,8 +677,9 @@ gremlin> g.V().valueMap().order()   // 3.6.0
 ----
 
 Feature tags have been introduced for feature tests that stress these new semantics (see Committer Documentation).
-A new GraphFeature has been added "OrderabilitySemantics" to signify compliance with the new cross type orderability
+A new GraphFeature has been added "OrderabilitySemantics" to signify compliance with the new comparability/orderability
 semantics.
 
+See: link:https://tinkerpop.apache.org/docs/3.6.0/dev/provider/#_comparability[Provider Documentation]
 See: link:https://tinkerpop.apache.org/docs/3.6.0/dev/provider/#_orderability[Provider Documentation]
 See: link:https://tinkerpop.apache.org/docs/3.6.0/dev/developer/#_for_committers[Developer Documentation]
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java
index 2339b90..304ae76 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java
@@ -18,7 +18,7 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal;
 
-import org.apache.tinkerpop.gremlin.util.NumberHelper;
+import org.apache.tinkerpop.gremlin.util.GremlinValueComparator;
 
 import java.util.function.BiPredicate;
 
@@ -34,17 +34,14 @@ import java.util.function.BiPredicate;
 public enum Compare implements BiPredicate<Object, Object> {
 
     /**
-     * Evaluates if the first object is equal to the second. If both are of type {@link Number}, {@link NumberHelper}
-     * will be used for the comparison, thus enabling the comparison of only values, ignoring the number types.
+     * Evaluates if the first object is equal to the second per Gremlin Comparison semantics.
      *
      * @since 3.0.0-incubating
      */
     eq {
         @Override
         public boolean test(final Object first, final Object second) {
-            return null == first ? null == second : (bothAreNumber(first, second)
-                    ? NumberHelper.compare((Number) first, (Number) second) == 0
-                    : first.equals(second));
+            return GremlinValueComparator.COMPARE.equals(first, second);
         }
 
         /**
@@ -57,8 +54,7 @@ public enum Compare implements BiPredicate<Object, Object> {
     },
 
     /**
-     * Evaluates if the first object is not equal to the second. If both are of type {@link Number}, {@link NumberHelper}
-     * will be used for the comparison, thus enabling the comparison of only values, ignoring the number types.
+     * Evaluates if the first object is not equal to the second per Gremlin Comparison semantics.
      *
      * @since 3.0.0-incubating
      */
@@ -78,25 +74,14 @@ public enum Compare implements BiPredicate<Object, Object> {
     },
 
     /**
-     * Evaluates if the first object is greater than the second. If both are of type {@link Number}, {@link NumberHelper}
-     * will be used for the comparison, thus enabling the comparison of only values, ignoring the number types.
+     * Evaluates if the first object is greater than the second per Gremlin Comparison semantics.
      *
      * @since 3.0.0-incubating
      */
     gt {
         @Override
         public boolean test(final Object first, final Object second) {
-            if (null != first && null != second) {
-                if (bothAreNumber(first, second)) {
-                    return NumberHelper.compare((Number) first, (Number) second) > 0;
-                }
-                if (bothAreComparableAndOfSameType(first, second)) {
-                    return ((Comparable) first).compareTo(second) > 0;
-                }
-                throwException(first, second);
-            }
-
-            return false;
+            return GremlinValueComparator.COMPARE.compare(first, second) > 0;
         }
 
         /**
@@ -109,15 +94,14 @@ public enum Compare implements BiPredicate<Object, Object> {
     },
 
     /**
-     * Evaluates if the first object is greater-equal to the second. If both are of type {@link Number}, {@link NumberHelper}
-     * will be used for the comparison, thus enabling the comparison of only values, ignoring the number types.
+     * Evaluates if the first object is greater-equal to the second per Gremlin Comparison semantics.
      *
      * @since 3.0.0-incubating
      */
     gte {
         @Override
         public boolean test(final Object first, final Object second) {
-            return null == first ? null == second : (null != second && !lt.test(first, second));
+            return GremlinValueComparator.COMPARE.compare(first, second) >= 0;
         }
 
         /**
@@ -130,24 +114,14 @@ public enum Compare implements BiPredicate<Object, Object> {
     },
 
     /**
-     * Evaluates if the first object is less than the second. If both are of type {@link Number}, {@link NumberHelper}
-     * will be used for the comparison, thus enabling the comparison of only values, ignoring the number types.
+     * Evaluates if the first object is less than the second per Gremlin Comparison semantics.
      *
      * @since 3.0.0-incubating
      */
     lt {
         @Override
         public boolean test(final Object first, final Object second) {
-            if (null != first && null != second) {
-                if (bothAreNumber(first, second)) {
-                    return NumberHelper.compare((Number) first, (Number) second) < 0;
-                }
-                if (bothAreComparableAndOfSameType(first, second)) {
-                    return ((Comparable) first).compareTo(second) < 0;
-                }
-                throwException(first, second);
-            }
-            return false;
+            return GremlinValueComparator.COMPARE.compare(first, second) < 0;
         }
 
         /**
@@ -160,15 +134,14 @@ public enum Compare implements BiPredicate<Object, Object> {
     },
 
     /**
-     * Evaluates if the first object is less-equal to the second. If both are of type {@link Number}, {@link NumberHelper}
-     * will be used for the comparison, thus enabling the comparison of only values, ignoring the number types.
+     * Evaluates if the first object is less-equal to the second per Gremlin Comparison semantics.
      *
      * @since 3.0.0-incubating
      */
     lte {
         @Override
         public boolean test(final Object first, final Object second) {
-            return null == first ? null == second : (null != second && !gt.test(first, second));
+            return GremlinValueComparator.COMPARE.compare(first, second) <= 0;
         }
 
         /**
@@ -180,19 +153,6 @@ public enum Compare implements BiPredicate<Object, Object> {
         }
     };
 
-    private static boolean bothAreNumber(Object first, Object second) {
-        return first instanceof Number && second instanceof Number;
-    }
-
-    private static boolean bothAreComparableAndOfSameType(Object first, Object second) {
-        return first instanceof Comparable && second instanceof Comparable
-                && (first.getClass().isInstance(second) || second.getClass().isInstance(first));
-    }
-
-    private static void throwException(Object first, Object second) {
-        throw new IllegalArgumentException(String.format("Cannot compare '%s' (%s) and '%s' (%s) as both need to be an instance of Number or Comparable (and of the same type)", first, first.getClass().getSimpleName(), second, second.getClass().getSimpleName()));
-    }
-
     /**
      * Produce the opposite representation of the current {@code Compare} enum.
      */
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Contains.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Contains.java
index 4358f9e..5a77811 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Contains.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Contains.java
@@ -52,9 +52,19 @@ public enum Contains implements BiPredicate<Object, Collection> {
     within {
         @Override
         public boolean test(final Object first, final Collection second) {
-            return first instanceof Number
-                    ? IteratorUtils.anyMatch(second.iterator(), o -> Compare.eq.test(first, o))
-                    : second.contains(first);
+            GremlinTypeErrorException typeError = null;
+            for (final Object o : second) {
+                try {
+                    if (Compare.eq.test(first, o))
+                        return true;
+                } catch (GremlinTypeErrorException ex) {
+                    // hold onto it until the end in case any other arguments evaluate to TRUE
+                    typeError = ex;
+                }
+            }
+            if (typeError != null)
+                throw typeError;
+            return false;
         }
     },
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinTypeErrorException.java
similarity index 51%
copy from gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java
copy to gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinTypeErrorException.java
index 72866a1..063a759 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinTypeErrorException.java
@@ -16,27 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.process.traversal.step.filter;
-
-import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
-import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
+package org.apache.tinkerpop.gremlin.process.traversal;
 
 /**
- * @author Marko A. Rodriguez (http://markorodriguez.com)
+ * We use this exception type to signify ERROR in Ternary Boolean Logics. This exception will never propagate to the
+ * user for boolean value expressions, it will always be ultimately reduced to FALSE.
  */
-public final class OrStep<S> extends ConnectiveStep<S> {
+public class GremlinTypeErrorException extends RuntimeException {
+    public GremlinTypeErrorException() {
+    }
+
+    public GremlinTypeErrorException(final String message) {
+        super(message);
+    }
 
-    public OrStep(final Traversal.Admin traversal, final Traversal<S, ?>... traversals) {
-        super(traversal, traversals);
+    public GremlinTypeErrorException(final String message, final Throwable cause) {
+        super(message, cause);
     }
 
-    @Override
-    protected boolean filter(final Traverser.Admin<S> traverser) {
-        for (final Traversal.Admin<S, ?> traversal : this.traversals) {
-            if (TraversalUtil.test(traverser, traversal))
-                return true;
-        }
-        return false;
+    public GremlinTypeErrorException(final Throwable cause) {
+        super(cause);
     }
-}
\ No newline at end of file
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Order.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Order.java
index 4bdb850..915196f 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Order.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Order.java
@@ -21,7 +21,7 @@ package org.apache.tinkerpop.gremlin.process.traversal;
 import java.util.Comparator;
 import java.util.Random;
 
-import org.apache.tinkerpop.gremlin.util.OrderabilityComparator;
+import org.apache.tinkerpop.gremlin.util.GremlinValueComparator;
 
 /**
  * Provides {@code Comparator} instances for ordering traversers.
@@ -58,7 +58,7 @@ public enum Order implements Comparator<Object> {
     asc {
         @Override
         public int compare(final Object first, final Object second) {
-            return OrderabilityComparator.INSTANCE.compare(first, second);
+            return ORDER.compare(first, second);
         }
 
         @Override
@@ -75,7 +75,7 @@ public enum Order implements Comparator<Object> {
     desc {
         @Override
         public int compare(final Object first, final Object second) {
-            return OrderabilityComparator.INSTANCE.compare(second, first);
+            return ORDER.compare(second, first);
         }
 
         @Override
@@ -84,7 +84,23 @@ public enum Order implements Comparator<Object> {
         }
     };
 
-    private static final Random RANDOM = new Random();
+    private static final Comparator<Object> ORDER = Comparator.comparing(
+            // first transform (strip Traverser layer and convert Enums)
+            Order::transform, GremlinValueComparator.ORDER);
+
+    /**
+     * Strip the Traverser layer and convert Enum to string.
+     */
+    private static Object transform(Object o) {
+        // we want to sort the underlying object contained by the traverser
+        if (o instanceof Traverser)
+            o = ((Traverser) o).get();
+        // need to convert enum to string representations for comparison or else you can get cast exceptions.
+        // this typically happens when sorting local on the keys of maps that contain T
+        if (o instanceof Enum)
+            o = ((Enum) o).name();
+        return o;
+    }
 
     /**
      * {@inheritDoc}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java
index 9c25825..bf61ca1 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java
@@ -37,6 +37,7 @@ public enum Text implements BiPredicate<String, String> {
     startingWith {
         @Override
         public boolean test(final String value, final String prefix) {
+            checkNull(value, prefix);
             return value.startsWith(prefix);
         }
 
@@ -77,6 +78,7 @@ public enum Text implements BiPredicate<String, String> {
     endingWith {
         @Override
         public boolean test(final String value, final String suffix) {
+            checkNull(value, suffix);
             return value.endsWith(suffix);
         }
 
@@ -117,6 +119,7 @@ public enum Text implements BiPredicate<String, String> {
     containing {
         @Override
         public boolean test(final String value, final String search) {
+            checkNull(value, search);
             return value.contains(search);
         }
 
@@ -149,6 +152,12 @@ public enum Text implements BiPredicate<String, String> {
         }
     };
 
+    private static final void checkNull(final String... args) {
+        for (String arg : args)
+            if (arg == null)
+                throw new GremlinTypeErrorException();
+    }
+
     /**
      * Produce the opposite representation of the current {@code Text} enum.
      */
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AndStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AndStep.java
index 5c20cd8..b8c7475 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AndStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AndStep.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.filter;
 
+import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
@@ -33,10 +34,18 @@ public final class AndStep<S> extends ConnectiveStep<S> {
 
     @Override
     protected boolean filter(final Traverser.Admin<S> traverser) {
+        GremlinTypeErrorException typeError = null;
         for (final Traversal.Admin<S, ?> traversal : this.traversals) {
-            if (!TraversalUtil.test(traverser, traversal))
-                return false;
+            try {
+                if (!TraversalUtil.test(traverser, traversal))
+                    return false;
+            } catch (GremlinTypeErrorException ex) {
+                // hold onto it until the end in case any other arguments evaluate to FALSE
+                typeError = ex;
+            }
         }
+        if (typeError != null)
+            throw typeError;
         return true;
     }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/BinaryReductionStep.java
similarity index 54%
copy from gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java
copy to gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/BinaryReductionStep.java
index 72866a1..c2373fe 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/BinaryReductionStep.java
@@ -18,25 +18,12 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.filter;
 
-import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
-import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
+import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException;
 
 /**
- * @author Marko A. Rodriguez (http://markorodriguez.com)
+ * Any {@link FilterStep} marked as a BinaryReductionStep will catch {@link GremlinTypeErrorException}s produced by
+ * boolean value expressions (predicates) contained within the filter and treat them as FALSE, thus filtering out
+ * the solution. This is a reduction from Ternary Boolean logics (TRUE, FALSE, ERROR) to ordinary boolean logics.
  */
-public final class OrStep<S> extends ConnectiveStep<S> {
-
-    public OrStep(final Traversal.Admin traversal, final Traversal<S, ?>... traversals) {
-        super(traversal, traversals);
-    }
-
-    @Override
-    protected boolean filter(final Traverser.Admin<S> traverser) {
-        for (final Traversal.Admin<S, ?> traversal : this.traversals) {
-            if (TraversalUtil.test(traverser, traversal))
-                return true;
-        }
-        return false;
-    }
-}
\ No newline at end of file
+public interface BinaryReductionStep {
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/FilterStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/FilterStep.java
index e07d951..3d0bd1e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/FilterStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/FilterStep.java
@@ -18,9 +18,11 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.filter;
 
+import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
@@ -34,9 +36,22 @@ public abstract class FilterStep<S> extends AbstractStep<S, S> {
     @Override
     protected Traverser.Admin<S> processNextStart() {
         while (true) {
-            final Traverser.Admin<S> traverser = this.starts.next();
-            if (this.filter(traverser))
-                return traverser;
+            try {
+                final Traverser.Admin<S> traverser = this.starts.next();
+                if (this.filter(traverser))
+                    return traverser;
+            } catch (GremlinTypeErrorException ex) {
+                if (this instanceof BinaryReductionStep || getTraversal().isRoot()) {
+                    /*
+                     * Either we are at a known reduction point (TraversalFilterStep, WhereTraversalStep), or we
+                     * are at the top level of the query. In either of these cases we do a binary reduction from
+                     * ERROR -> FALSE and filter the solution quietly.
+                     */
+                } else {
+                    // not a ternary -> binary reducer, pass the ERROR on
+                    throw ex;
+                }
+            }
         }
     }
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java
index 72866a1..2e4f0a9 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.filter;
 
+import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
@@ -33,10 +34,18 @@ public final class OrStep<S> extends ConnectiveStep<S> {
 
     @Override
     protected boolean filter(final Traverser.Admin<S> traverser) {
+        GremlinTypeErrorException typeError = null;
         for (final Traversal.Admin<S, ?> traversal : this.traversals) {
-            if (TraversalUtil.test(traverser, traversal))
-                return true;
+            try {
+                if (TraversalUtil.test(traverser, traversal))
+                    return true;
+            } catch (GremlinTypeErrorException ex) {
+                // hold onto it until the end in case any other arguments evaluate to TRUE
+                typeError = ex;
+            }
         }
+        if (typeError != null)
+            throw typeError;
         return false;
     }
 }
\ No newline at end of file
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TraversalFilterStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TraversalFilterStep.java
index 773e389..63aa62d 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TraversalFilterStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TraversalFilterStep.java
@@ -34,7 +34,7 @@ import java.util.Set;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class TraversalFilterStep<S> extends FilterStep<S> implements TraversalParent, Configuring {
+public final class TraversalFilterStep<S> extends FilterStep<S> implements TraversalParent, Configuring, BinaryReductionStep {
     private final Parameters parameters = new Parameters();
 
     private Traversal.Admin<S, ?> filterTraversal;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java
index 264575d..dde9003 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java
@@ -42,7 +42,7 @@ import java.util.Set;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class WhereTraversalStep<S> extends FilterStep<S> implements TraversalParent, Scoping, PathProcessor {
+public final class WhereTraversalStep<S> extends FilterStep<S> implements TraversalParent, Scoping, PathProcessor, BinaryReductionStep {
 
     protected Traversal.Admin<?, ?> whereTraversal;
     protected final Set<String> scopeKeys = new HashSet<>();
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
index 4f375e5..b9bd614 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/DotNetTranslator.java
@@ -177,6 +177,10 @@ public final class DotNetTranslator implements Translator.ScriptTranslator {
 
         @Override
         protected String getSyntax(final Number o) {
+            if (o instanceof Float && Float.isNaN((float) o) )
+                return "Single.NaN";
+            if (o instanceof Double && Double.isNaN((double) o) )
+                return "Double.NaN";
             return o.toString();
         }
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
index ef544b4..42eaa67 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java
@@ -184,9 +184,9 @@ public final class GroovyTranslator implements Translator.ScriptTranslator {
             if (o instanceof Long)
                 return o + "L";
             else if (o instanceof Double)
-                return o + "d";
+                return Double.isNaN((double) o) ? "Double.NaN" : o + "d";
             else if (o instanceof Float)
-                return o + "f";
+                return Float.isNaN((float) o) ? "Float.NaN" : o + "f";
             else if (o instanceof Integer)
                 return "(int) " + o;
             else if (o instanceof Byte)
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/AndP.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/AndP.java
index fc42461..cb9dfc9 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/AndP.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/AndP.java
@@ -18,7 +18,9 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.util;
 
+import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 
 import java.io.Serializable;
@@ -79,10 +81,18 @@ public final class AndP<V> extends ConnectiveP<V> {
 
         @Override
         public boolean test(final V valueA, final V valueB) {
+            GremlinTypeErrorException typeError = null;
             for (final P<V> predicate : this.andP.predicates) {
-                if (!predicate.test(valueA))
-                    return false;
+                try {
+                    if (!predicate.test(valueA))
+                        return false;
+                } catch (GremlinTypeErrorException ex) {
+                    // hold onto it until the end in case any other arguments evaluate to FALSE
+                    typeError = ex;
+                }
             }
+            if (typeError != null)
+                throw typeError;
             return true;
         }
     }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/OrP.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/OrP.java
index 6bf906f..ee28340 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/OrP.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/OrP.java
@@ -18,7 +18,9 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.util;
 
+import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 
 import java.io.Serializable;
@@ -79,10 +81,18 @@ public final class OrP<V> extends ConnectiveP<V> {
 
         @Override
         public boolean test(final V valueA, final V valueB) {
+            GremlinTypeErrorException typeError = null;
             for (final P<V> predicate : this.orP.predicates) {
-                if (predicate.test(valueA))
-                    return true;
+                try {
+                    if (predicate.test(valueA))
+                        return true;
+                } catch (GremlinTypeErrorException ex) {
+                    // hold onto it until the end in case any other arguments evaluate to TRUE
+                    typeError = ex;
+                }
             }
+            if (typeError != null)
+                throw typeError;
             return false;
         }
     }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java
new file mode 100644
index 0000000..9bdd718
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java
@@ -0,0 +1,330 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.util;
+
+import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException;
+import org.apache.tinkerpop.gremlin.process.traversal.Path;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Element;
+import org.apache.tinkerpop.gremlin.structure.Property;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+
+import java.util.Comparator;
+import java.util.Date;
+import java.util.EnumMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import static org.apache.tinkerpop.gremlin.util.NumberHelper.eitherAreNaN;
+
+/**
+ * An implementation of the Comparability/Orderability semantics as defined in the Apache TinkerPop Provider
+ * documentation.
+ *
+ * @author Mike Personick (http://github.com/mikepersonick)
+ */
+public abstract class GremlinValueComparator implements Comparator<Object> {
+
+    /**
+     * Orderability comparator allows for a total order across all types (no type error exceptions).
+     */
+    public static final GremlinValueComparator ORDER = new GremlinValueComparator() {
+
+        /**
+         * Compare two Gremlin value objects per the Orderability semantics.
+         */
+        @Override
+        public int compare(final Object f, final Object s) {
+            // nulls first
+            if (f == null || s == null)
+                return f == s ? 0 : f == null ? -1 : 1;
+
+            final Type ft = Type.type(f);
+            /*
+             * Do a quick check to catch very common cases - class equality or both numerics. Even if these are false we
+             * could still be dealing with the same type (e.g. different implementation of List), but let the type
+             * lookup method handle that.
+             */
+            final Type st = f.getClass().equals(s.getClass()) || (f instanceof Number && s instanceof Number) ?
+                    ft : Type.type(s);
+
+            return ft != st ? ft.priority() - st.priority() : comparator(ft).compare(f, s);
+        }
+
+        /**
+         * Test for equivalence (Orderability semantics).
+         */
+        public boolean equals(final Object f, final Object s) {
+            return compare(f, s) == 0;
+        }
+    };
+
+    /**
+     * Compare has very similar semantics to orderability with the following exceptions:
+     *
+     * 1. NaN is not equal to anything, including itself, and cannot be compared to anything:
+     *      equals(NaN, anything) = FALSE
+     *      compare(NaN, anything) = ERROR
+     * 2. Unlike Orderability, Comparability is limited to a single type space:
+     *      compare(type1, type2) = ERROR
+     *
+     * Note that because of type errors for Comparability, equals(a,b) does not necessarily produce the same result
+     * as compare(a,b) == 0. Make sure to use equals(a,b) for P.eq/neq.
+     */
+    public static final GremlinValueComparator COMPARE = new GremlinValueComparator() {
+
+        /**
+         * Compare two Gremlin value objects per the Comparability semantics. Throws type errors for NaN comparison
+         * and for cross-type comparison (including nulltype).
+         *
+         * Use this method for P.lt/lte/gt/gte.
+         */
+        @Override
+        public int compare(final Object f, final Object s) {
+            // For Compare, NaN always produces ERROR
+            if (eitherAreNaN(f, s))
+                throwTypeError();
+
+            // For Compare we do not cross type boundaries, including null
+            if (!comparable(f, s))
+                throwTypeError();
+
+            // comparable(f, s) assures that type(f) == type(s)
+            final Type type = Type.type(f);
+            return comparator(type).compare(f, s);
+        }
+
+        /**
+         * Test two Gremlin values for equality per the Comparability semantics. Returns false for NaN comparison
+         * and for cross-type comparison (including nulltype).
+         *
+         * Use this method for P.eq/neq.
+         */
+        @Override
+        public boolean equals(final Object f, final Object s) {
+            try {
+                return compare(f, s) == 0;
+            } catch (GremlinTypeErrorException ex) {
+                /**
+                 * By routing through the compare(f, s) path we expose ourselves to type errors, which should be
+                 * reduced to false for equality:
+                 *
+                 * compare(NaN, anything) -> ERROR -> FALSE for equality
+                 * compare(Type1, Type2) -> ERROR -> FALSE for equality
+                 *
+                 * Can also happen for elements nested inside of collections.
+                 */
+                return false;
+            }
+        }
+    };
+
+    private static <T> T throwTypeError() {
+        throw new GremlinTypeErrorException();
+    }
+
+    /**
+     * Boolean, Date, String, UUID.
+     */
+    private final Comparator<Comparable> naturalOrderComparator = Comparator.naturalOrder();
+
+    /**
+     * This comparator does not provide a stable order for numerics because of type promotion equivalence semantics.
+     */
+    private final Comparator<Number> numberComparator = (f,s) -> NumberHelper.compare(f, s);
+
+    /**
+     * Sort Vertex, Edge, VertexProperty by id.
+     */
+    private final Comparator<Element> elementComparator =
+            Comparator.comparing(Element::id, this);
+
+    /**
+     * Sort Property first by key, then by value.
+     */
+    private final Comparator<Property> propertyComparator =
+            Comparator.<Property,Object>comparing(Property::key, this).thenComparing(Property::value, this);
+
+    /**
+     * Sort List, Set, Path, and Map element-by-element in the order presented by their natural iterator.
+     */
+    private final Comparator<Iterable> iterableComparator = (f, s) -> {
+        final Iterator fi = f.iterator();
+        final Iterator si = s.iterator();
+
+        while (fi.hasNext() && si.hasNext()) {
+            final int i = this.compare(fi.next(), si.next());
+            if (i != 0) {
+                return i;
+            }
+        }
+
+        return fi.hasNext() ? 1 : si.hasNext() ? -1 : 0;
+    };
+
+    /**
+     * Sort Map by entry-set.
+     */
+    private final Comparator<Map> mapComparator =
+            Comparator.comparing(Map::entrySet, iterableComparator);
+
+    /**
+     * Sort Map.Entry first by key, then by value.
+     */
+    private final Comparator<Map.Entry> entryComparator =
+            Comparator.<Map.Entry,Object>comparing(Map.Entry::getKey, this).thenComparing(Map.Entry::getValue, this);
+
+    /**
+     * Sort using either their natural order if they are Comparable or by classname then by toString() if they are
+     * not naturally Comparable. Also handles the special case: f.equals(s) -> 0 even for objects without a natural
+     * comparator.
+     */
+    private final Comparator<Object> unknownTypeComparator = (f, s) -> naturallyComparable(f, s)
+        ? naturallyCompare(f, s)
+        : Comparator.comparing(o -> o.getClass().getName()).thenComparing(Object::toString).compare(f, s);
+
+    /**
+     * Two nulls. Always 0.
+     */
+    private final Comparator<Object> nulltypeComparator = (f, s) -> 0;
+
+    /**
+     * The typespace. The ordinal of the type indicates its position in cross-type ordering.
+     */
+    public enum Type {
+        Nulltype,
+        Boolean         (Boolean.class),
+        Number          (Number.class),
+        Date            (Date.class),
+        String          (String.class),
+        UUID            (UUID.class),
+        Vertex          (Vertex.class),
+        Edge            (Edge.class),
+        VertexProperty  (VertexProperty.class),
+        Property        (Property.class),
+        Path            (Path.class),
+        Set             (Set.class),
+        List            (List.class),
+        Map             (Map.class),
+        MapEntry        (Map.Entry.class),
+        Unknown         (Object.class);
+
+        /**
+         * Lookup by instanceof semantics (not class equality). Current implementation will return first enum value
+         * that matches the object's type.
+         */
+        public static Type type(final Object o) {
+            if (o == null)
+                return Nulltype;
+
+            final Type[] types = Type.values();
+            for (int i = 1; i < types.length; i++) {
+                if (types[i].type.isInstance(o)) {
+                    return types[i];
+                }
+            }
+            return Unknown;
+        }
+
+        Type() {
+            this.type = null;
+        }
+        Type(final Class type) {
+            this.type = type;
+        }
+        private final Class type;
+
+        public int priority() {
+            return ordinal();
+        }
+    }
+
+    /**
+     * Compare the two objects using their natural comparator. Also handles the special case: f.equals(s) -> 0 even
+     * for objects without a natural comparator.
+     */
+    private static int naturallyCompare(final Object f, final Object s) {
+        if (f instanceof Comparable && s instanceof Comparable)
+            return ((Comparable) f).compareTo(s);
+        return f.equals(s) ? 0 : throwTypeError();
+    }
+
+    /**
+     * Return true if the two objects in the UNKNOWN type space are equal or comparable via their own natural Comparator.
+     */
+    private static boolean naturallyComparable(final Object f, final Object s) {
+        return (f instanceof Comparable && s instanceof Comparable
+                && (f.getClass().isInstance(s) || s.getClass().isInstance(f)))
+                || f.equals(s);
+    }
+
+    /**
+     * Return true if the two objects are of the same comparison type (although they may not be the exact same Class)
+     */
+    private static boolean comparable(final Object f, final Object s) {
+        if (f == null || s == null)
+            return f == s; // true iff both in the null space
+
+        final Type ft = Type.type(f);
+        final Type st = Type.type(s);
+
+        // Check for same type. If they're both the unknown type then return true iff they are naturally Comparable
+        return ft == Type.Unknown && st == Type.Unknown ? naturallyComparable(f, s) : ft == st;
+    }
+
+    private final Map<Type, Comparator> comparators = new EnumMap<Type, Comparator>(Type.class) {{
+        put(Type.Nulltype,       nulltypeComparator);
+        put(Type.Boolean,        naturalOrderComparator);
+        put(Type.Number,         numberComparator);
+        put(Type.Date,           naturalOrderComparator);
+        put(Type.String,         naturalOrderComparator);
+        put(Type.UUID,           naturalOrderComparator);
+        put(Type.Vertex,         elementComparator);
+        put(Type.Edge,           elementComparator);
+        put(Type.VertexProperty, elementComparator);
+        put(Type.Property,       propertyComparator);
+        put(Type.Path,           iterableComparator);
+        put(Type.Set,            iterableComparator);
+        put(Type.List,           iterableComparator);
+        put(Type.Map,            mapComparator);
+        put(Type.MapEntry,       entryComparator);
+        put(Type.Unknown,        unknownTypeComparator);
+    }};
+    
+    private GremlinValueComparator() {}
+
+    protected Comparator comparator(final Type type) {
+        return comparators.get(type);
+    }
+
+    /**
+     * Compare(a,b) is defined differently for Comparablity vs. Orderability.
+     */
+    public abstract int compare(final Object f, final Object s);
+
+    /**
+     * Equals(a,b) is defined differently for Comparablity (equality) vs. Orderability (equivalence).
+     */
+    public abstract boolean equals(final Object f, final Object s);
+
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java
index d2110d6..47bb9c5 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java
@@ -429,10 +429,20 @@ public final class NumberHelper {
      *     a = null, b = 1 -> 1
      *     a = 1, b = null -> 1
      *     a = null, b = null -> null
+     *     a = NaN, b = 1 -> 1
+     *     a = 1, b = NaN -> 1
+     *     a = NaN, b = NaN -> NaN
      * </pre>
      */
     public static Number min(final Number a, final Number b) {
-        if (null == a && null == b) return null;
+        // handle one or both null (propagate null if both)
+        if (a == null || b == null)
+            return a == null ? b : a;
+
+        // handle one or both NaN (propagate NaN if both)
+        if (eitherAreNaN(a, b))
+            return isNaN(a) ? b : a;
+
         final Class<? extends Number> clazz = getHighestCommonNumberClass(a, b);
         return getHelper(clazz).min.apply(a, b);
     }
@@ -445,24 +455,27 @@ public final class NumberHelper {
      *     a = null, b = 1 -> 1
      *     a = 1, b = null -> 1
      *     a = null, b = null -> null
+     *     a = NaN, b = 1 -> 1
+     *     a = 1, b = NaN -> 1
+     *     a = NaN, b = NaN -> NaN
      * </pre>
      */
     public static Comparable min(final Comparable a, final Comparable b) {
-        if (null == a || null == b) {
-            if (null == a && null == b)
-                return null;
-            else
-                return null == a ? b : a;
-        }
+        // handle one or both null (propagate null if both)
+        if (a == null || b == null)
+            return a == null ? b : a;
 
-        if (a instanceof Number && b instanceof Number && !a.equals(Double.NaN) && !b.equals(Double.NaN)) {
+        // handle one or both NaN (propagate NaN if both)
+        if (eitherAreNaN(a, b))
+            return isNaN(a) ? b : a;
+
+        if (a instanceof Number && b instanceof Number) {
             final Number an = (Number) a, bn = (Number) b;
             final Class<? extends Number> clazz = getHighestCommonNumberClass(an, bn);
             return (Comparable) getHelper(clazz).min.apply(an, bn);
+        } else {
+            return a.compareTo(b) < 0 ? a : b;
         }
-        return isNonValue(a) ? b :
-                isNonValue(b) ? a :
-                        a.compareTo(b) < 0 ? a : b;
     }
 
     /**
@@ -473,10 +486,20 @@ public final class NumberHelper {
      *     a = null, b = 1 -> 1
      *     a = 1, b = null -> 1
      *     a = null, b = null -> null
+     *     a = NaN, b = 1 -> 1
+     *     a = 1, b = NaN -> 1
+     *     a = NaN, b = NaN -> NaN
      * </pre>
      */
     public static Number max(final Number a, final Number b) {
-        if (null == a && null == b) return null;
+        // handle one or both null (propagate null if both)
+        if (a == null || b == null)
+            return a == null ? b : a;
+
+        // handle one or both NaN (propagate NaN if both)
+        if (eitherAreNaN(a, b))
+            return isNaN(a) ? b : a;
+
         final Class<? extends Number> clazz = getHighestCommonNumberClass(a, b);
         return getHelper(clazz).max.apply(a, b);
     }
@@ -489,28 +512,31 @@ public final class NumberHelper {
      *     a = null, b = 1 -> 1
      *     a = 1, b = null -> 1
      *     a = null, b = null -> null
+     *     a = NaN, b = 1 -> 1
+     *     a = 1, b = NaN -> 1
+     *     a = NaN, b = NaN -> NaN
      * </pre>
      */
     public static Comparable max(final Comparable a, final Comparable b) {
-        if (null == a || null == b) {
-            if (null == a && null == b)
-                return null;
-            else
-                return null == a ? b : a;
-        }
+        // handle one or both null (propagate null if both)
+        if (a == null || b == null)
+            return a == null ? b : a;
+
+        // handle one or both NaN (propagate NaN if both)
+        if (eitherAreNaN(a, b))
+            return isNaN(a) ? b : a;
 
-        if (a instanceof Number && b instanceof Number && !a.equals(Double.NaN) && !b.equals(Double.NaN)) {
+        if (a instanceof Number && b instanceof Number) {
             final Number an = (Number) a, bn = (Number) b;
             final Class<? extends Number> clazz = getHighestCommonNumberClass(an, bn);
             return (Comparable) getHelper(clazz).max.apply(an, bn);
+        } else {
+            return a.compareTo(b) > 0 ? a : b;
         }
-        return isNonValue(a) ? b :
-                isNonValue(b) ? a :
-                        a.compareTo(b) > 0 ? a : b;
     }
 
     /**
-     * Compares two numbers.
+     * Compares two numbers. Follows orderability semantics for NaN, which places NaN after +Inf.
      *
      * <pre>
      *     a = 4, b = 2 -> 1
@@ -518,15 +544,19 @@ public final class NumberHelper {
      *     a = null, b = 1 -> -1
      *     a = 1, b = null -> 1
      *     a = null, b = null -> 0
+     *     a = NaN, b = NaN -> 0
+     *     a = NaN, b = Inf -> 1
      * </pre>
      */
     public static Integer compare(final Number a, final Number b) {
-        if (null == a || null == b) {
-            if (a == b)
-                return 0;
-            else
-                return null == a ? -1 : 1;
-        }
+        // handle one or both null
+        if (a == null || b == null)
+            return a == b ? 0 : (a == null ? -1 : 1);
+
+        // handle one or both NaN
+        if (eitherAreNaN(a, b))
+            return (bothAreNaN(a, b)) ? 0 : isNaN(a) ? 1 : -1;
+
         final Class<? extends Number> clazz = getHighestCommonNumberClass(a, b);
         return getHelper(clazz).cmp.apply(a, b);
     }
@@ -589,10 +619,20 @@ public final class NumberHelper {
     }
 
     private static boolean isNumber(final Number number) {
-        return number != null && !number.equals(Double.NaN);
+        return number != null && !isNaN(number);
     }
 
-    private static boolean isNonValue(final Object value) {
-        return value instanceof Double && !isNumber((Double) value);
+    public static boolean isNaN(final Object object) {
+        return (object instanceof Float && Float.isNaN((float) object)) ||
+                (object instanceof Double && Double.isNaN((double) object));
     }
+
+    public static boolean eitherAreNaN(final Object first, final Object second) {
+        return isNaN(first) || isNaN(second);
+    }
+
+    public static boolean bothAreNaN(final Object first, final Object second) {
+        return isNaN(first) && isNaN(second);
+    }
+
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/OrderabilityComparator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/OrderabilityComparator.java
deleted file mode 100644
index e1e3d07..0000000
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/OrderabilityComparator.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.tinkerpop.gremlin.util;
-
-import org.apache.tinkerpop.gremlin.process.traversal.Path;
-import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.structure.Edge;
-import org.apache.tinkerpop.gremlin.structure.Element;
-import org.apache.tinkerpop.gremlin.structure.Property;
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.tinkerpop.gremlin.structure.VertexProperty;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-
-/**
- * An implementation of the Comparability/Orderability semantics as defined in the Apache TinkerPop Provider
- * documentation.
- *
- * @author Mike Personick
- */
-public class OrderabilityComparator implements Comparator<Object> {
-
-    public static final Comparator<Object> INSTANCE = Comparator.comparing(
-            // first transform (strip Traverser layer)
-            OrderabilityComparator::transform,
-                    // then handle nulls
-                    Comparator.nullsFirst(
-                            // then consider orderability
-                            new OrderabilityComparator()));
-
-    /**
-     * Boolean, Date, String, UUID.
-     */
-    private static final Comparator<Comparable> naturalOrderComparator = Comparator.naturalOrder();
-
-    /**
-     * This comparator does not provide a stable order for numerics because of type promotion equivalence semantics.
-     */
-    private static final Comparator<Number> numberComparator = (f,s) -> NumberHelper.compare(f, s);
-
-    /**
-     * Sort Vertex, Edge, VertexProperty by id.
-     */
-    private static final Comparator<Element> elementComparator =
-            Comparator.comparing(Element::id, INSTANCE);
-
-    /**
-     * Sort Property first by key, then by value.
-     */
-    private static final Comparator<Property> propertyComparator =
-            Comparator.<Property,Object>comparing(Property::key, INSTANCE).thenComparing(Property::value, INSTANCE);
-
-    /**
-     * Sort List, Set, Path, and Map element-by-element in the order presented by their natural iterator.
-     */
-    private static final Comparator<Iterable> iteratableComparator = (f, s) -> {
-        final Iterator fi = f.iterator();
-        final Iterator si = s.iterator();
-
-        while (fi.hasNext() && si.hasNext()) {
-            final int i = INSTANCE.compare(fi.next(), si.next());
-            if (i != 0) {
-                return i;
-            }
-        }
-
-        return fi.hasNext() ? 1 : si.hasNext() ? -1 : 0;
-    };
-
-    /**
-     * Sort Map by entry-set.
-     */
-    private static final Comparator<Map> mapComparator =
-            Comparator.comparing(Map::entrySet, iteratableComparator);
-
-    /**
-     * Sort Map.Entry first by key, then by value.
-     */
-    private static final Comparator<Map.Entry> entryComparator =
-            Comparator.<Map.Entry,Object>comparing(Map.Entry::getKey, INSTANCE).thenComparing(Map.Entry::getValue, INSTANCE);
-
-    /**
-     * Sort unknown types first by classname, then by natural toString().
-     */
-    private static final Comparator<Object> unknownTypeComparator =
-            Comparator.comparing(f -> f.getClass().getName()).thenComparing(Object::toString);
-
-    /**
-     * The typespace, along with their priorities and the comparators used to compare within each type.
-     */
-    private enum Type {
-        Boolean         (Boolean.class,        0,  naturalOrderComparator),
-        Number          (Number.class,         1,  numberComparator),
-        Date            (Date.class,           2,  naturalOrderComparator),
-        String          (String.class,         3,  naturalOrderComparator),
-        UUID            (UUID.class,           4,  naturalOrderComparator),
-        Vertex          (Vertex.class,         5,  elementComparator),
-        Edge            (Edge.class,           6,  elementComparator),
-        VertexProperty  (VertexProperty.class, 7,  elementComparator),
-        Property        (Property.class,       8,  propertyComparator),
-        Path            (Path.class,           9,  iteratableComparator),
-        Set             (Set.class,            10, iteratableComparator),
-        List            (List.class,           11, iteratableComparator),
-        Map             (Map.class,            12, mapComparator),
-        MapEntry        (Map.Entry.class,      13, entryComparator),
-        Unknown         (Object.class,         14, unknownTypeComparator);
-
-        /**
-         * Lookup by instanceof semantics (not class equality). Current implementation will return first enum value
-         * that matches the object's type.
-         */
-        public static Type type(final Object o) {
-            final Type[] types = Type.values();
-            for (int i = 0; i < types.length; i++) {
-                if (types[i].type.isInstance(o)) {
-                    return types[i];
-                }
-            }
-            return Unknown;
-        }
-
-        Type(Class type, int priority, Comparator comparator) {
-            this.type = type;
-            this.priority = priority;
-            this.comparator = comparator;
-        }
-        private final Class type;
-        private final int priority;
-        private final Comparator comparator;
-    }
-
-    /**
-     * Strip the Traverser layer and convert Enum to string.
-     */
-    private static Object transform(Object o) {
-        // we want to sort the underlying object contained by the traverser
-        if (o instanceof Traverser)
-            o = ((Traverser) o).get();
-        // need to convert enum to string representations for comparison or else you can get cast exceptions.
-        // this typically happens when sorting local on the keys of maps that contain T
-        if (o instanceof Enum)
-            o = ((Enum) o).name();
-        return o;
-    }
-
-    private OrderabilityComparator() {}
-
-    /**
-     * Compare two non-null Gremlin value objects per the Comparability/Orderability semantics.
-     */
-    @Override
-    public int compare(final Object f, final Object s) {
-        final Type ft = Type.type(f);
-        /*
-         * Do a quick check to catch very common cases - class equality or both numerics. Even if these are false we
-         * could still be dealing with the same type (e.g. different implementation of Vertex), but let the type lookup
-         * method handle that.
-         */
-        final Type st = f.getClass().equals(s.getClass()) || (f instanceof Number && s instanceof Number) ?
-                        ft : Type.type(s);
-
-        return ft != st ? ft.priority - st.priority : ft.comparator.compare(f, s);
-    }
-
-}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/CollectionFactory.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/CollectionFactory.java
new file mode 100644
index 0000000..9e25631
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/CollectionFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.util.tools;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+public class CollectionFactory {
+
+    public static <E> List<E> asList(final E... elements) {
+        return new ArrayList<>(Arrays.asList(elements));
+    }
+
+    public static <E> LinkedHashSet<E> asSet(final E... elements) {
+        return asSet(Arrays.asList(elements));
+    }
+
+    public static <E> LinkedHashSet<E> asSet(final Collection<E> elements) {
+        return new LinkedHashSet<>(elements);
+    }
+
+    public static <K,V> LinkedHashMap<K,V> asMap(final Object... elements) {
+        final LinkedHashMap<K,V> map = new LinkedHashMap<>();
+        for (int i = 0; i < elements.length; i+=2) {
+            final K k = (K) elements[i];
+            final V v = (V) (i+1 < elements.length ? elements[i+1] : null);
+            map.put(k, v);
+        }
+        return map;
+    }
+
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/MultiMap.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/MultiMap.java
index 749fa43..0a5bf7b 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/MultiMap.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/tools/MultiMap.java
@@ -20,7 +20,6 @@ package org.apache.tinkerpop.gremlin.util.tools;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareExceptionTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareExceptionTest.java
deleted file mode 100644
index 7111b29..0000000
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareExceptionTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.tinkerpop.gremlin.process.traversal;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * @author Eduard Tudenhoefner
- */
-@RunWith(Parameterized.class)
-public class CompareExceptionTest {
-
-    @Rule
-    public ExpectedException exceptionRule = ExpectedException.none();
-
-    @Parameterized.Parameters(name = "{0}.test({1},{2}) = {3}")
-    public static Iterable<Object[]> dataThatThrowsException() {
-        final List<Object[]> testCases = new ArrayList<>(Arrays.asList(new Object[][]{
-                {Compare.gt, new Object(), new Object(), IllegalArgumentException.class},
-                {Compare.lt, new Object(), new Object(), IllegalArgumentException.class},
-                {Compare.gte, new Object(), new Object(), IllegalArgumentException.class},
-                {Compare.lte, new Object(), new Object(), IllegalArgumentException.class},
-                {Compare.gt, 23, "23", IllegalArgumentException.class},
-                {Compare.gte, 23, "23", IllegalArgumentException.class},
-                {Compare.lt, 23, "23", IllegalArgumentException.class},
-                {Compare.lte, 23, "23", IllegalArgumentException.class},
-                {Compare.lte, new CompareTest.A(), new CompareTest.B(), IllegalArgumentException.class},
-                {Compare.lte, new CompareTest.B(), new CompareTest.A(), IllegalArgumentException.class},
-                {Compare.lte, new CompareTest.C(), new CompareTest.D(), IllegalArgumentException.class},
-        }));
-        return testCases;
-    }
-
-    @Parameterized.Parameter(value = 0)
-    public Compare compare;
-
-    @Parameterized.Parameter(value = 1)
-    public Object first;
-
-    @Parameterized.Parameter(value = 2)
-    public Object second;
-
-    @Parameterized.Parameter(value = 3)
-    public Class<? extends Throwable> expectedException;
-
-    @Test
-    public void shouldThrowException() {
-        exceptionRule.expect(expectedException);
-        exceptionRule.expectMessage("as both need to be an instance of Number or Comparable (and of the same type)");
-        compare.test(first, second);
-    }
-}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java
index ee2918d..7e03bc6 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java
@@ -18,7 +18,11 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal;
 
+import org.apache.tinkerpop.gremlin.util.tools.CollectionFactory;
+import org.javatuples.Pair;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -29,6 +33,9 @@ import java.util.Arrays;
 import java.util.List;
 
 import static org.junit.Assert.assertEquals;
+import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asList;
+import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asMap;
+import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asSet;
 
 /**
  * @author Stephen Mallette (http://stephen.genoprime.com)
@@ -37,54 +44,43 @@ import static org.junit.Assert.assertEquals;
 @RunWith(Parameterized.class)
 public class CompareTest {
 
+    @Rule
+    public ExpectedException exceptionRule = ExpectedException.none();
+
+    private static final Object NaN = new Object() {
+        public String toString() { return "NaN"; }
+    };
+
     @Parameterized.Parameters(name = "{0}.test({1},{2}) = {3}")
     public static Iterable<Object[]> data() {
         final List<Object[]> testCases = new ArrayList<>(Arrays.asList(new Object[][]{
-                {Compare.eq, null, null, true},
-                {Compare.eq, null, 1, false},
-                {Compare.eq, 1, null, false},
                 {Compare.eq, "1", "1", true},
                 {Compare.eq, 100, 99, false},
                 {Compare.eq, 100, 101, false},
                 {Compare.eq, "z", "a", false},
                 {Compare.eq, "a", "z", false},
                 {Compare.eq, new Object(), new Object(), false},
-                {Compare.neq, null, null, false},
-                {Compare.neq, null, 1, true},
-                {Compare.neq, 1, null, true},
                 {Compare.neq, "1", "1", false},
                 {Compare.neq, 100, 99, true},
                 {Compare.neq, 100, 101, true},
                 {Compare.neq, "z", "a", true},
                 {Compare.neq, "a", "z", true},
                 {Compare.neq, new Object(), new Object(), true},
-                {Compare.gt, null, null, false},
-                {Compare.gt, null, 1, false},
-                {Compare.gt, 1, null, false},
                 {Compare.gt, "1", "1", false},
                 {Compare.gt, 100, 99, true},
                 {Compare.gt, 100, 101, false},
                 {Compare.gt, "z", "a", true},
                 {Compare.gt, "a", "z", false},
-                {Compare.lt, null, null, false},
-                {Compare.lt, null, 1, false},
-                {Compare.lt, 1, null, false},
                 {Compare.lt, "1", "1", false},
                 {Compare.lt, 100, 99, false},
                 {Compare.lt, 100, 101, true},
                 {Compare.lt, "z", "a", false},
                 {Compare.lt, "a", "z", true},
-                {Compare.gte, null, null, true},
-                {Compare.gte, null, 1, false},
-                {Compare.gte, 1, null, false},
                 {Compare.gte, "1", "1", true},
                 {Compare.gte, 100, 99, true},
                 {Compare.gte, 100, 101, false},
                 {Compare.gte, "z", "a", true},
                 {Compare.gte, "a", "z", false},
-                {Compare.lte, null, null, true},
-                {Compare.lte, null, 1, false},
-                {Compare.lte, 1, null, false},
                 {Compare.lte, "1", "1", true},
                 {Compare.lte, 100, 99, false},
                 {Compare.lte, 100, 101, true},
@@ -95,7 +91,141 @@ public class CompareTest {
                 {Compare.gte, new B(), new C(), true},
                 {Compare.lte, new B(), new D(), true},
                 {Compare.lte, new C(), new C(), true},
-                {Compare.lte, new D(), new D(), true}
+                {Compare.lte, new D(), new D(), true},
+
+                // type promotion
+                {Compare.eq,  1, 1.0d, true},
+                {Compare.neq, 1, 1.0d, false},
+                {Compare.lt,  1, 1.0d, false},
+                {Compare.lte, 1, 1.0d, true},
+                {Compare.gt,  1, 1.0d, false},
+                {Compare.gte, 1, 1.0d, true},
+
+                // Incomparable types produce ERROR
+                {Compare.gt,  23, "23", GremlinTypeErrorException.class},
+                {Compare.gte, 23, "23", GremlinTypeErrorException.class},
+                {Compare.lt,  23, "23", GremlinTypeErrorException.class},
+                {Compare.lte, 23, "23", GremlinTypeErrorException.class},
+                {Compare.lte, new CompareTest.A(), new CompareTest.B(), GremlinTypeErrorException.class},
+                {Compare.lte, new CompareTest.B(), new CompareTest.A(), GremlinTypeErrorException.class},
+                {Compare.lte, new CompareTest.C(), new CompareTest.D(), GremlinTypeErrorException.class},
+                {Compare.gte, new Object(), new Object(), GremlinTypeErrorException.class},
+                {Compare.gte, new Object(), new Object(), GremlinTypeErrorException.class},
+                {Compare.gte, new Object(), new Object(), GremlinTypeErrorException.class},
+
+                /*
+                 * NaN has pretty much the same comparability behavior against any argument (including itself):
+                 * P.eq(NaN, any) = FALSE
+                 * P.neq(NaN, any) = TRUE
+                 * P.lt/lte/gt/gte(NaN, any) = ERROR -> FALSE
+                 */
+                {Compare.eq,  NaN, NaN, false},
+                {Compare.neq, NaN, NaN, true},
+                {Compare.gt,  NaN, NaN, GremlinTypeErrorException.class},
+                {Compare.gte, NaN, NaN, GremlinTypeErrorException.class},
+                {Compare.lt,  NaN, NaN, GremlinTypeErrorException.class},
+                {Compare.lte, NaN, NaN, GremlinTypeErrorException.class},
+
+                {Compare.eq,  NaN, 0, false},
+                {Compare.neq, NaN, 0, true},
+                {Compare.gt,  NaN, 0, GremlinTypeErrorException.class},
+                {Compare.gte, NaN, 0, GremlinTypeErrorException.class},
+                {Compare.lt,  NaN, 0, GremlinTypeErrorException.class},
+                {Compare.lte, NaN, 0, GremlinTypeErrorException.class},
+
+                {Compare.eq,  NaN, "foo", false},
+                {Compare.neq, NaN, "foo", true},
+                {Compare.gt,  NaN, "foo", GremlinTypeErrorException.class},
+                {Compare.gte, NaN, "foo", GremlinTypeErrorException.class},
+                {Compare.lt,  NaN, "foo", GremlinTypeErrorException.class},
+                {Compare.lte, NaN, "foo", GremlinTypeErrorException.class},
+
+                /*
+                 * We consider null to be in its own type space, and thus not comparable (lt/lte/gt/gte) with
+                 * anything other than null:
+                 *
+                 * P.eq(null, any non-null) = FALSE
+                 * P.neq(null, any non-null) = TRUE
+                 * P.lt/lte/gt/gte(null, any non-null) = ERROR -> FALSE
+                 */
+                {Compare.eq,  null, null, true},
+                {Compare.neq, null, null, false},
+                {Compare.gt,  null, null, false},
+                {Compare.gte, null, null, true},
+                {Compare.lt,  null, null, false},
+                {Compare.lte, null, null, true},
+
+                {Compare.eq,  "foo", null, false},
+                {Compare.neq, "foo", null, true},
+                {Compare.gt,  "foo", null, GremlinTypeErrorException.class},
+                {Compare.gte, "foo", null, GremlinTypeErrorException.class},
+                {Compare.lt,  "foo", null, GremlinTypeErrorException.class},
+                {Compare.lte, "foo", null, GremlinTypeErrorException.class},
+
+                {Compare.eq,  null, 1, false},
+                {Compare.eq,  1, null, false},
+                {Compare.neq, null, 1, true},
+                {Compare.neq, 1, null, true},
+                {Compare.gt,  null, 1, GremlinTypeErrorException.class},
+                {Compare.gt,  1, null, GremlinTypeErrorException.class},
+                {Compare.gte, null, 1, GremlinTypeErrorException.class},
+                {Compare.gte, 1, null, GremlinTypeErrorException.class},
+                {Compare.lt,  null, 1, GremlinTypeErrorException.class},
+                {Compare.lt,  1, null, GremlinTypeErrorException.class},
+                {Compare.lte, null, 1, GremlinTypeErrorException.class},
+                {Compare.lte, 1, null, GremlinTypeErrorException.class},
+
+                {Compare.eq,  NaN, null, false},
+                {Compare.neq, NaN, null, true},
+                {Compare.gt,  NaN, null, GremlinTypeErrorException.class},
+                {Compare.gte, NaN, null, GremlinTypeErrorException.class},
+                {Compare.lt,  NaN, null, GremlinTypeErrorException.class},
+                {Compare.lte, NaN, null, GremlinTypeErrorException.class},
+
+                /*
+                 * Collections
+                 */
+                {Compare.eq, asList(0), asList(0), true},
+                {Compare.neq, asList(0), asList(0), false},
+                {Compare.lt, asList(0), asList(0), false},
+                {Compare.lte, asList(0), asList(0), true},
+                {Compare.gt, asList(0), asList(0), false},
+                {Compare.gte, asList(0), asList(0), true},
+
+                {Compare.eq, asList(0), asList(1), false},
+                {Compare.neq, asList(0), asList(1), true},
+                {Compare.lt, asList(0), asList(1), true},
+                {Compare.lte, asList(0), asList(1), true},
+                {Compare.gt, asList(0), asList(1), false},
+                {Compare.gte, asList(0), asList(1), false},
+
+                {Compare.eq, asList(Double.NaN), asList(Double.NaN), false},
+                {Compare.neq, asList(Double.NaN), asList(Double.NaN), true},
+                {Compare.lt, asList(Double.NaN), asList(Double.NaN), GremlinTypeErrorException.class},
+                {Compare.lte, asList(Double.NaN), asList(Double.NaN), GremlinTypeErrorException.class},
+                {Compare.gt, asList(Double.NaN), asList(Double.NaN), GremlinTypeErrorException.class},
+                {Compare.gte, asList(Double.NaN), asList(Double.NaN), GremlinTypeErrorException.class},
+
+                {Compare.eq, asList(Double.NaN), asList(0), false},
+                {Compare.neq, asList(Double.NaN), asList(0), true},
+                {Compare.lt, asList(Double.NaN), asList(0), GremlinTypeErrorException.class},
+                {Compare.lte, asList(Double.NaN), asList(0), GremlinTypeErrorException.class},
+                {Compare.gt, asList(Double.NaN), asList(0), GremlinTypeErrorException.class},
+                {Compare.gte, asList(Double.NaN), asList(0), GremlinTypeErrorException.class},
+
+                {Compare.eq, asMap(1, 1), asMap(1, null), false},
+                {Compare.neq, asMap(1, 1), asMap(1, null), true},
+                {Compare.lt, asMap(1, 1), asMap(1, null), GremlinTypeErrorException.class},
+                {Compare.lte, asMap(1, 1), asMap(1, null), GremlinTypeErrorException.class},
+                {Compare.gt, asMap(1, 1), asMap(1, null), GremlinTypeErrorException.class},
+                {Compare.gte, asMap(1, 1), asMap(1, null), GremlinTypeErrorException.class},
+
+                {Compare.eq, asList(0), asList("foo"), false},
+                {Compare.neq, asList(0), asList("foo"), true},
+                {Compare.lt, asList(0), asList("foo"), GremlinTypeErrorException.class},
+                {Compare.lte, asList(0), asList("foo"), GremlinTypeErrorException.class},
+                {Compare.gt, asList(0), asList("foo"), GremlinTypeErrorException.class},
+                {Compare.gte, asList(0), asList("foo"), GremlinTypeErrorException.class},
         }));
         // Compare Numbers of mixed types.
         final List<Object> one = Arrays.asList(1, 1l, 1d, 1f, BigDecimal.ONE, BigInteger.ONE);
@@ -229,11 +359,34 @@ public class CompareTest {
     public Object second;
 
     @Parameterized.Parameter(value = 3)
-    public boolean expected;
+    public Object expected;
 
     @Test
     public void shouldTest() {
-        assertEquals(expected, compare.test(first, second));
+        if (expected instanceof Class)
+            exceptionRule.expect((Class) expected);
+
+        if (first == NaN || second == NaN) {
+            // test all the NaN combos
+            final List<Pair> args = new ArrayList<>();
+            if (first == NaN && second == NaN) {
+                args.add(new Pair(Double.NaN, Double.NaN));
+                args.add(new Pair(Double.NaN, Float.NaN));
+                args.add(new Pair(Float.NaN, Double.NaN));
+                args.add(new Pair(Float.NaN, Float.NaN));
+            } else if (first == NaN) {
+                args.add(new Pair(Double.NaN, second));
+                args.add(new Pair(Float.NaN, second));
+            } else {
+                args.add(new Pair(first, Double.NaN));
+                args.add(new Pair(first, Float.NaN));
+            }
+            for (Pair arg : args) {
+                assertEquals(expected, compare.test(arg.getValue0(), arg.getValue1()));
+            }
+        } else {
+            assertEquals(expected, compare.test(first, second));
+        }
     }
 
     static class A implements Comparable<A> {
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ConnectiveTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ConnectiveTest.java
new file mode 100644
index 0000000..6e77f8d
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ConnectiveTest.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal;
+
+import org.apache.tinkerpop.gremlin.process.traversal.util.AndP;
+import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Mike Personick (http://github.com/mikepersonick)
+ */
+@RunWith(Enclosed.class)
+public class ConnectiveTest {
+
+    private static final Object VAL = 1;
+    private static final P TRUE = P.eq(1);
+    private static final P FALSE = P.gt(1);
+    private static final P ERROR = P.lt(Double.NaN);
+
+    @RunWith(Parameterized.class)
+    public static class OrTest {
+        @Rule
+        public ExpectedException exceptionRule = ExpectedException.none();
+
+        @Parameterized.Parameters(name = "Or.test({0},{1}) = {2}")
+        public static Iterable<Object[]> data() {
+            return Arrays.asList(new Object[][]{
+                    {TRUE, TRUE, true},
+                    {TRUE, FALSE, true},
+                    {TRUE, ERROR, true},
+                    {FALSE, TRUE, true},
+                    {FALSE, FALSE, false},
+                    {FALSE, ERROR, GremlinTypeErrorException.class},
+                    {ERROR, TRUE, true},
+                    {ERROR, FALSE, GremlinTypeErrorException.class},
+                    {ERROR, ERROR, GremlinTypeErrorException.class},
+            });
+        }
+
+        @Parameterized.Parameter(value = 0)
+        public P first;
+
+        @Parameterized.Parameter(value = 1)
+        public P second;
+
+        @Parameterized.Parameter(value = 2)
+        public Object expected;
+
+        @Test
+        public void shouldTest() {
+            if (expected instanceof Class)
+                exceptionRule.expect((Class) expected);
+
+            assertEquals(expected, new OrP(Arrays.asList(first,second)).test(VAL));
+        }
+    }
+
+    @RunWith(Parameterized.class)
+    public static class AndTest {
+        @Rule
+        public ExpectedException exceptionRule = ExpectedException.none();
+
+        @Parameterized.Parameters(name = "And.test({0},{1}) = {2}")
+        public static Iterable<Object[]> data() {
+            return Arrays.asList(new Object[][]{
+                    {TRUE, TRUE, true},
+                    {TRUE, FALSE, false},
+                    {TRUE, ERROR, GremlinTypeErrorException.class},
+                    {FALSE, TRUE, false},
+                    {FALSE, FALSE, false},
+                    {FALSE, ERROR, false},
+                    {ERROR, TRUE, GremlinTypeErrorException.class},
+                    {ERROR, FALSE, false},
+                    {ERROR, ERROR, GremlinTypeErrorException.class},
+            });
+        }
+
+        @Parameterized.Parameter(value = 0)
+        public P first;
+
+        @Parameterized.Parameter(value = 1)
+        public P second;
+
+        @Parameterized.Parameter(value = 2)
+        public Object expected;
+
+        @Test
+        public void shouldTest() {
+            if (expected instanceof Class)
+                exceptionRule.expect((Class) expected);
+
+            assertEquals(expected, new AndP(Arrays.asList(first,second)).test(VAL));
+        }
+    }
+
+    @RunWith(Parameterized.class)
+    public static class XorTest {
+        @Rule
+        public ExpectedException exceptionRule = ExpectedException.none();
+
+        @Parameterized.Parameters(name = "Xor.test({0},{1}) = {2}")
+        public static Iterable<Object[]> data() {
+            return Arrays.asList(new Object[][]{
+                    {TRUE, TRUE, false},
+                    {TRUE, FALSE, true},
+                    {TRUE, ERROR, GremlinTypeErrorException.class},
+                    {FALSE, TRUE, true},
+                    {FALSE, FALSE, false},
+                    {FALSE, ERROR, GremlinTypeErrorException.class},
+                    {ERROR, TRUE, GremlinTypeErrorException.class},
+                    {ERROR, FALSE, GremlinTypeErrorException.class},
+                    {ERROR, ERROR, GremlinTypeErrorException.class},
+            });
+        }
+
+        @Parameterized.Parameter(value = 0)
+        public P first;
+
+        @Parameterized.Parameter(value = 1)
+        public P second;
+
+        @Parameterized.Parameter(value = 2)
+        public Object expected;
+
+        @Test
+        public void shouldTest() {
+            if (expected instanceof Class)
+                exceptionRule.expect((Class) expected);
+
+            final P xor = new OrP(Arrays.asList(new AndP(Arrays.asList(first,second.negate())),
+                                                new AndP(Arrays.asList(first.negate(),second))));
+
+            assertEquals(expected, xor.test(VAL));
+        }
+    }
+}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ContainsTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ContainsTest.java
index e61764e..4e449e7 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ContainsTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ContainsTest.java
@@ -18,7 +18,9 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal;
 
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -35,6 +37,9 @@ import static org.junit.Assert.assertEquals;
 @RunWith(Parameterized.class)
 public class ContainsTest {
 
+    @Rule
+    public ExpectedException exceptionRule = ExpectedException.none();
+
     @Parameterized.Parameters(name = "{0}({1},{2}) = {3}")
     public static Iterable<Object[]> data() {
         return new ArrayList<>(Arrays.asList(new Object[][]{
@@ -47,7 +52,12 @@ public class ContainsTest {
                 {Contains.within, 100, Arrays.asList(1, 2, 3, 4, 10), false},
                 {Contains.without, 10L, Arrays.asList(1, 2, 3, 4, 10), false},
                 {Contains.within, "test", Arrays.asList(1, 2, 3, "test", 10), true},
-                {Contains.without, "testing", Arrays.asList(1, 2, 3, "test", 10), true}
+                {Contains.without, "testing", Arrays.asList(1, 2, 3, "test", 10), true},
+
+                {Contains.within, Double.NaN, Arrays.asList(Double.NaN), false},
+                {Contains.within, Double.NaN, Arrays.asList(0, Double.NaN), false},
+                {Contains.without, Double.NaN, Arrays.asList(Double.NaN), true},
+                {Contains.without, Double.NaN, Arrays.asList(0, Double.NaN), true},
         }));
     }
 
@@ -61,10 +71,13 @@ public class ContainsTest {
     public Collection collection;
 
     @Parameterized.Parameter(value = 3)
-    public boolean expected;
+    public Object expected;
 
     @Test
     public void shouldTest() {
+        if (expected instanceof Class)
+            exceptionRule.expect((Class) expected);
+
         assertEquals(expected, contains.test(first, collection));
     }
 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java
index 0204399..4dc54a8 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java
@@ -18,8 +18,13 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal;
 
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.MutablePath;
 import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.util.GremlinValueComparator;
+import org.apache.tinkerpop.gremlin.util.tools.CollectionFactory;
+import org.javatuples.Pair;
 import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -28,57 +33,156 @@ import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 
+import static org.apache.tinkerpop.gremlin.util.GremlinValueComparator.Type;
+import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asList;
+import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asMap;
+import static org.apache.tinkerpop.gremlin.util.tools.CollectionFactory.asSet;
+
 import static org.junit.Assert.assertEquals;
 
-/**
- * @author Stephen Mallette (http://stephen.genoprime.com)
- */
-@RunWith(Parameterized.class)
+@RunWith(Enclosed.class)
 public class OrderTest {
 
-    private static final SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
+    /**
+     * @author Stephen Mallette (http://stephen.genoprime.com)
+     */
+    @RunWith(Parameterized.class)
+    public static class OrderListTest {
 
-    @Parameterized.Parameters(name = "{0}.test({1},{2})")
-    public static Iterable<Object[]> data() throws ParseException {
-        return new ArrayList<>(Arrays.asList(new Object[][]{
-                {Order.asc, Arrays.asList("a", "c", null, "d"), Arrays.asList(null, "a", "c", "d")},
-                {Order.asc, Arrays.asList("b", "a", "c", "d"), Arrays.asList("a", "b", "c", "d")},
-                {Order.desc, Arrays.asList("b", "a", "c", "d"), Arrays.asList("d", "c", "b", "a")},
-                {Order.desc, Arrays.asList("c", "a", null, "d"), Arrays.asList("d", "c", "a", null)},
-                {Order.asc, Arrays.asList(formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2020"), formatter.parse("1-Jan-2008")),
+        private static final SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
+
+        @Parameterized.Parameters(name = "{0}.sort({1}) = {2}")
+        public static Iterable<Object[]> data() throws ParseException {
+            return new ArrayList<>(Arrays.asList(new Object[][]{
+                    {Order.asc, Arrays.asList("a", "c", null, "d"), Arrays.asList(null, "a", "c", "d")},
+                    {Order.asc, Arrays.asList("b", "a", "c", "d"), Arrays.asList("a", "b", "c", "d")},
+                    {Order.desc, Arrays.asList("b", "a", "c", "d"), Arrays.asList("d", "c", "b", "a")},
+                    {Order.desc, Arrays.asList("c", "a", null, "d"), Arrays.asList("d", "c", "a", null)},
+                    {Order.asc, Arrays.asList(formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2020"), formatter.parse("1-Jan-2008")),
                             Arrays.asList(formatter.parse("1-Jan-2008"), formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2020"))},
-                {Order.desc, Arrays.asList(formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2020"), formatter.parse("1-Jan-2008")),
-                             Arrays.asList(formatter.parse("1-Jan-2020"), formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2008"))},
-                {Order.desc, Arrays.asList(100L, 1L, null, -1L, 0L), Arrays.asList(100L, 1L, 0L, -1L, null)},
-                {Order.desc, Arrays.asList(100L, 1L, -1L, 0L), Arrays.asList(100L, 1L, 0L, -1L)},
-                {Order.asc, Arrays.asList(100L, 1L, null, -1L, 0L), Arrays.asList(null, -1L, 0L, 1L, 100L)},
-                {Order.asc, Arrays.asList(100.1f, 1.1f, -1.1f, 0.1f), Arrays.asList(-1.1f, 0.1f, 1.1f, 100.1f)},
-                {Order.desc, Arrays.asList(100.1f, 1.1f, -1.1f, 0.1f), Arrays.asList(100.1f, 1.1f, 0.1f, -1.1f)},
-                {Order.asc, Arrays.asList(100.1d, 1.1d, -1.1d, 0.1d), Arrays.asList(-1.1d, 0.1d, 1.1d, 100.1d)},
-                {Order.desc, Arrays.asList(100.1d, 1.1d, -1.1d, 0.1d), Arrays.asList(100.1d, 1.1d, 0.1d, -1.1d)},
-                {Order.asc, Arrays.asList(100L, 1L, -1L, 0L), Arrays.asList(-1L, 0L, 1L, 100L)},
-                {Order.desc, Arrays.asList(100L, 1L, -1L, 0L), Arrays.asList(100L, 1L, 0L, -1L)},
-                {Order.asc, Arrays.asList(100, 1, -1, 0), Arrays.asList(-1, 0, 1, 100)},
-                {Order.desc, Arrays.asList(100, 1, -1, 0), Arrays.asList(100, 1, 0, -1)},
-                {Order.asc, Arrays.asList("b", "a", T.id, "c", "d"), Arrays.asList("a", "b", "c", "d", T.id)},
-                {Order.desc, Arrays.asList("b", "a", T.id, "c", "d"), Arrays.asList(T.id, "d", "c", "b", "a")}}));
+                    {Order.desc, Arrays.asList(formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2020"), formatter.parse("1-Jan-2008")),
+                            Arrays.asList(formatter.parse("1-Jan-2020"), formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2008"))},
+                    {Order.desc, Arrays.asList(100L, 1L, null, -1L, 0L), Arrays.asList(100L, 1L, 0L, -1L, null)},
+                    {Order.desc, Arrays.asList(100L, 1L, -1L, 0L), Arrays.asList(100L, 1L, 0L, -1L)},
+                    {Order.asc, Arrays.asList(100L, 1L, null, -1L, 0L), Arrays.asList(null, -1L, 0L, 1L, 100L)},
+                    {Order.asc, Arrays.asList(100.1f, 1.1f, -1.1f, 0.1f), Arrays.asList(-1.1f, 0.1f, 1.1f, 100.1f)},
+                    {Order.desc, Arrays.asList(100.1f, 1.1f, -1.1f, 0.1f), Arrays.asList(100.1f, 1.1f, 0.1f, -1.1f)},
+                    {Order.asc, Arrays.asList(100.1d, 1.1d, -1.1d, 0.1d), Arrays.asList(-1.1d, 0.1d, 1.1d, 100.1d)},
+                    {Order.desc, Arrays.asList(100.1d, 1.1d, -1.1d, 0.1d), Arrays.asList(100.1d, 1.1d, 0.1d, -1.1d)},
+                    {Order.asc, Arrays.asList(100L, 1L, -1L, 0L), Arrays.asList(-1L, 0L, 1L, 100L)},
+                    {Order.desc, Arrays.asList(100L, 1L, -1L, 0L), Arrays.asList(100L, 1L, 0L, -1L)},
+                    {Order.asc, Arrays.asList(100, 1, -1, 0), Arrays.asList(-1, 0, 1, 100)},
+                    {Order.desc, Arrays.asList(100, 1, -1, 0), Arrays.asList(100, 1, 0, -1)},
+                    {Order.asc, Arrays.asList("b", "a", T.id, "c", "d"), Arrays.asList("a", "b", "c", "d", T.id)},
+                    {Order.desc, Arrays.asList("b", "a", T.id, "c", "d"), Arrays.asList(T.id, "d", "c", "b", "a")}}));
+        }
+
+        @Parameterized.Parameter(value = 0)
+        public Order order;
+
+        @Parameterized.Parameter(value = 1)
+        public Object toBeOrdered;
+
+        @Parameterized.Parameter(value = 2)
+        public Object expectedOrder;
+
+        @Test
+        public void shouldOrder() {
+            Collections.sort((List) toBeOrdered, order);
+            assertEquals(expectedOrder, toBeOrdered);
+        }
     }
 
-    @Parameterized.Parameter(value = 0)
-    public Order order;
+    /**
+     * @author Mike Personick (http://github.com/mikepersonick)
+     */
+    @RunWith(Parameterized.class)
+    public static class OrderabilityTest {
+
+        private static final Comparator order = GremlinValueComparator.ORDER;
 
-    @Parameterized.Parameter(value = 1)
-    public Object toBeOrdered;
+        private static final Object NaN = new Object() {
+            public String toString() { return "NaN"; }
+        };
 
-    @Parameterized.Parameter(value = 2)
-    public Object expectedOrder;
+        @Parameterized.Parameters(name = "Order.compare({0},{1}) = {2}")
+        public static Iterable<Object[]> data() throws ParseException {
+            return new ArrayList<>(Arrays.asList(new Object[][]{
+                    // NaN tests - compares to 0 for itself, but larger than anything else including +Inf
+                    {NaN, NaN, 0},
+                    {null, NaN, -1},
+                    {NaN, null, 1},
+                    {null, null, 0},
+                    {NaN, 0, 1},
+                    {0, NaN, -1},
+                    {NaN, Double.POSITIVE_INFINITY, 1},
+                    {NaN, Float.POSITIVE_INFINITY, 1},
 
-    @Test
-    public void shouldOrder() {
-        Collections.sort((List) toBeOrdered, order);
-        assertEquals(expectedOrder, toBeOrdered);
+                    // type promotion means no stable order for Numbers
+                    {1, 1.0d, 0},
+                    {1.0d, 1, 0},
+
+                    // cross type
+                    {"foo", 1, Type.String.priority() - Type.Number.priority()},
+                    {MutablePath.make(), Collections.emptySet(), Type.Path.priority() - Type.Set.priority()},
+                    
+                    // Collections
+                    {asList(1, 2, 3), asList(1, 2, 3), 0},
+                    {asList(1, 2, 3), asList(1, 2, 3, 4), -1},
+                    {asList(1, 2, 4), asList(1, 2, 3, 4), 1},
+                    {asSet(1, 2, 3), asSet(1, 2, 3), 0},
+                    {asSet(1, 2, 3), asSet(1, 2, 3, 4), -1},
+                    {asSet(1, 2, 4), asSet(1, 2, 3, 4), 1},
+                    {asMap(1, 1, 2, 2, 3, 3), asMap(1, 1, 2, 2, 3, 3), 0},
+                    {asMap(1, 1, 2, 2, 3, 3), asMap(1, 1, 2, 2, 3, 3, 4, 4), -1},
+                    {asMap(1, 1, 2, "foo", 3, 3), asMap(1, 1, 2, 2, 3, 3), Type.String.priority() - Type.Number.priority()},
+                    {asList(Double.NaN), asList(Float.NaN), 0},
+                    {asList(Double.NaN), asList(0), 1},
+                    {asList(0), asList(Double.NaN), -1},
+                    {asMap(1, 1), asMap(1, null), 1},
+                    {asList(0), asList("foo"), Type.Number.priority() - Type.String.priority()},
+
+            }));
+        }
+
+        @Parameterized.Parameter(value = 0)
+        public Object a;
+
+        @Parameterized.Parameter(value = 1)
+        public Object b;
+
+        @Parameterized.Parameter(value = 2)
+        public Integer expected;
+
+        @Test
+        public void shouldOrder() {
+            if (a == NaN || b == NaN) {
+                // test all the NaN combos
+                final List<Pair> args = new ArrayList<>();
+                if (a == NaN && b == NaN) {
+                    args.add(new Pair(Double.NaN, Double.NaN));
+                    args.add(new Pair(Double.NaN, Float.NaN));
+                    args.add(new Pair(Float.NaN, Double.NaN));
+                    args.add(new Pair(Float.NaN, Float.NaN));
+                } else if (a == NaN) {
+                    args.add(new Pair(Double.NaN, b));
+                    args.add(new Pair(Float.NaN, b));
+                } else {
+                    args.add(new Pair(a, Double.NaN));
+                    args.add(new Pair(a, Float.NaN));
+                }
+                for (final Pair arg : args) {
+                    assertEquals(expected.longValue(), order.compare(arg.getValue0(), arg.getValue1()));
+                }
+            } else {
+                assertEquals(expected.longValue(), order.compare(a, b));
+            }
+        }
     }
+
 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java
index e58e8be..b9e8102 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java
@@ -22,8 +22,10 @@ import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
 import org.apache.tinkerpop.gremlin.process.traversal.util.AndP;
 import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.runners.Enclosed;
+import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -47,6 +49,8 @@ public class PTest {
 
     @RunWith(Parameterized.class)
     public static class ParameterizedTest {
+        @Rule
+        public ExpectedException exceptionRule = ExpectedException.none();
 
         @Parameterized.Parameters(name = "{0}.test({1}) = {2}")
         public static Iterable<Object[]> data() {
@@ -97,6 +101,7 @@ public class PTest {
                     {P.outside(1, 10), 1, false},
                     {P.outside(1, 10), 9, false},
                     {P.outside(1, 10), 10, false},
+                    {P.outside(1, Double.NaN), 0, true},
                     {P.within(), 0, false},
                     {P.within((Object) null), 0, false},
                     {P.within((Object) null), null, true},
@@ -158,6 +163,20 @@ public class PTest {
                     {TextP.containing("o").and(P.gte("j")).and(TextP.endingWith("ko")), "josh", false},
                     {TextP.containing("o").and(P.gte("j").and(TextP.endingWith("ko"))), "marko", true},
                     {TextP.containing("o").and(P.gte("j").and(TextP.endingWith("ko"))), "josh", false},
+
+                    // type errors
+                    {P.outside(Double.NaN, Double.NaN), 0, GremlinTypeErrorException.class},
+                    {P.inside(-1, Double.NaN), 0, GremlinTypeErrorException.class},
+                    {P.inside(Double.NaN, 1), 0, GremlinTypeErrorException.class},
+                    {TextP.containing(null), "abc", GremlinTypeErrorException.class},
+                    {TextP.containing("abc"), null, GremlinTypeErrorException.class},
+                    {TextP.containing(null), null, GremlinTypeErrorException.class},
+                    {TextP.startingWith(null), "abc", GremlinTypeErrorException.class},
+                    {TextP.startingWith("abc"), null, GremlinTypeErrorException.class},
+                    {TextP.startingWith(null), null, GremlinTypeErrorException.class},
+                    {TextP.endingWith(null), "abc", GremlinTypeErrorException.class},
+                    {TextP.endingWith("abc"), null, GremlinTypeErrorException.class},
+                    {TextP.endingWith(null), null, GremlinTypeErrorException.class},
             }));
         }
 
@@ -168,10 +187,13 @@ public class PTest {
         public Object value;
 
         @Parameterized.Parameter(value = 2)
-        public boolean expected;
+        public Object expected;
 
         @Test
         public void shouldTest() {
+            if (expected instanceof Class)
+                exceptionRule.expect((Class) expected);
+
             assertEquals(expected, predicate.test(value));
             assertNotEquals(expected, predicate.clone().negate().test(value));
             assertNotEquals(expected, P.not(predicate.clone()).test(value));
diff --git a/gremlin-test/features/semantics/Orderability.feature b/gremlin-test/features/semantics/Orderability.feature
index f132a3e..a77f65d 100644
--- a/gremlin-test/features/semantics/Orderability.feature
+++ b/gremlin-test/features/semantics/Orderability.feature
@@ -15,7 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
-@StepClassOrderability
+@StepClassSemantics
 Feature: Orderability
 
   Scenario: g_V_values_order
@@ -118,6 +118,7 @@ Feature: Orderability
       | d[0.4].d |
       | d[0.2].d |
 
+  @GraphComputerVerificationInjectionNotSupported
   Scenario: g_inject_order
     Given the empty graph
     And using the parameter xx1 defined as "null"
@@ -153,6 +154,7 @@ Feature: Orderability
       | m[{"a":"a", "b":false, "c":"c"}] |
       | m[{"a":"a", "b":"b"}] |
 
+  @GraphComputerVerificationInjectionNotSupported
   Scenario: g_inject_order_byXdescX
     Given the empty graph
     And using the parameter xx1 defined as "null"
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/AbstractGremlinProcessTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/AbstractGremlinProcessTest.java
index 09044de..d8bb0da 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/AbstractGremlinProcessTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/AbstractGremlinProcessTest.java
@@ -164,6 +164,13 @@ public abstract class AbstractGremlinProcessTest extends AbstractGremlinTest {
         }
     }
 
+    // for basic filtering tests
+    public static void checkHasNext(final boolean expected, final Traversal traversal) {
+        final List results = traversal.toList();
+        assertThat(traversal.hasNext(), is(false));
+        assertThat(results.size() > 0, is(expected)); // easier for debugging this way
+    }
+
     private static <A> boolean internalCheckList(final List<A> expectedList, final List<A> actualList) {
         if (expectedList.size() != actualList.size()) {
             return false;
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
index e9c4602..b84cea2 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessLimitedStandardSuite.java
@@ -24,64 +24,12 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalEngine;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalInterruptionTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.ComplexTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.OrderabilityTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.BranchTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.ChooseTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.LocalTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.OptionalTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.RepeatTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.branch.UnionTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.AndTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.CoinTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.CyclicPathTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.DedupTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.DropTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.FilterTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.IsTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.OrTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.SampleTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.SimplePathTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.TailTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.filter.WhereTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.CoalesceTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ConstantTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.CountTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ElementMapTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.FlatMapTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.FoldTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.IndexTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.LoopsTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MapTest;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TernaryBooleanLogicsTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.MatchTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MathTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MaxTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MeanTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MinTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.OrderTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.PathTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.ProfileTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ProjectTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertiesTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ReadTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.SumTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.UnfoldTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.ValueMapTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.map.WriteTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AggregateTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.ExplainTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.GroupCountTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.GroupTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SackTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SideEffectCapTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SideEffectTest;
-import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.StoreTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.SubgraphTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.TreeTest;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ElementIdStrategyProcessTest;
@@ -93,8 +41,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.Transl
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.EarlyLimitStrategyProcessTest;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.IncidentToAdjacentStrategyProcessTest;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategyProcessTest;
-import org.apache.tinkerpop.gremlin.structure.Graph;
-import org.apache.tinkerpop.gremlin.structure.StructureStandardSuite;
 import org.junit.runners.model.InitializationError;
 import org.junit.runners.model.RunnerBuilder;
 
@@ -144,6 +90,7 @@ public class ProcessLimitedStandardSuite extends AbstractGremlinSuite {
 
             // semantics
             OrderabilityTest.Traversals.class,
+            TernaryBooleanLogicsTest.class,
     };
 
     /**
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java
index ac8dc99..1497bb4 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java
@@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalEngine;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalInterruptionTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.ComplexTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.OrderabilityTest;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TernaryBooleanLogicsTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.branch.BranchTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.branch.ChooseTest;
 import org.apache.tinkerpop.gremlin.process.traversal.step.branch.LocalTest;
@@ -204,6 +205,7 @@ public class ProcessStandardSuite extends AbstractGremlinSuite {
 
             // semantics
             OrderabilityTest.Traversals.class,
+            TernaryBooleanLogicsTest.class,
     };
 
     /**
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java
index 33dcb92..df7c95c 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java
@@ -27,6 +27,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Property;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.util.tools.CollectionFactory;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -54,19 +55,10 @@ public abstract class OrderabilityTest extends AbstractGremlinProcessTest {
         Date date = new Date();
         List list1 = Arrays.asList(1, 2, 3);
         List list2 = Arrays.asList(1, 2, 3, 4);
-        Set set1 = new LinkedHashSet(list1);
-        Set set2 = new LinkedHashSet(list2);
-        Map map1 = new LinkedHashMap() {{
-            put(1, 11);
-            put(2, 22);
-            put(3, false);
-            put(4, 44);
-        }};
-        Map map2 = new LinkedHashMap() {{
-            put(1, 11);
-            put(2, 22);
-            put(3, 33);
-        }};
+        Set set1 = CollectionFactory.asSet(list1);
+        Set set2 = CollectionFactory.asSet(list2);
+        Map map1 = CollectionFactory.asMap(1, 11, 2, 22, 3, false, 4, 44);
+        Map map2 = CollectionFactory.asMap(1, 11, 2, 22, 33);
 
         Object[] unordered = { map2, 1, map1, "foo", null, list1, date, set1, list2, true, uuid, "bar", 2.0, false, set2 };
     }
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TernaryBooleanLogicsTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TernaryBooleanLogicsTest.java
new file mode 100644
index 0000000..340355e
--- /dev/null
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TernaryBooleanLogicsTest.java
@@ -0,0 +1,321 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step;
+
+import org.apache.tinkerpop.gremlin.FeatureRequirement;
+import org.apache.tinkerpop.gremlin.process.AbstractGremlinProcessTest;
+import org.apache.tinkerpop.gremlin.process.GremlinProcessRunner;
+import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.BiFunction;
+
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.and;
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.is;
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.not;
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.or;
+import static org.apache.tinkerpop.gremlin.structure.Graph.Features.GraphFeatures;
+
+/**
+ * @author Mike Personick (http://github.com/mikepersonick)
+ */
+@RunWith(GremlinProcessRunner.class)
+public class TernaryBooleanLogicsTest extends AbstractGremlinProcessTest {
+
+    /**
+     * NaN comparisons always produce UNDEF comparison, reducing to FALSE in ternary->binary reduction.
+     */
+    @Test
+    @FeatureRequirement(featureClass = GraphFeatures.class, feature = GraphFeatures.FEATURE_ORDERABILITY_SEMANTICS)
+    public void testCompareNaN() {
+        // NaN vs. NaN
+        // P.eq
+        checkHasNext(false, g.inject(Double.NaN).is(P.eq(Double.NaN)));
+        // P.neq
+        checkHasNext(true, g.inject(Double.NaN).is(P.neq(Double.NaN)));
+        // P.lt
+        checkHasNext(false, g.inject(Double.NaN).is(P.lt(Double.NaN)));
+        // P.lte
+        checkHasNext(false, g.inject(Double.NaN).is(P.lte(Double.NaN)));
+        // P.gt
+        checkHasNext(false, g.inject(Double.NaN).is(P.gt(Double.NaN)));
+        // P.gte
+        checkHasNext(false, g.inject(Double.NaN).is(P.gte(Double.NaN)));
+
+        // non-NaN vs. NaN
+        // P.eq
+        checkHasNext(false, g.inject(1.0d).is(P.eq(Double.NaN)));
+        checkHasNext(false, g.inject(Double.NaN).is(P.eq(1.0d)));
+        // P.neq
+        checkHasNext(true, g.inject(1.0d).is(P.neq(Double.NaN)));
+        checkHasNext(true, g.inject(Double.NaN).is(P.neq(1.0d)));
+        // P.lt
+        checkHasNext(false, g.inject(1.0d).is(P.lt(Double.NaN)));
+        checkHasNext(false, g.inject(Double.NaN).is(P.lt(1.0d)));
+        // P.lte
+        checkHasNext(false, g.inject(1.0d).is(P.lte(Double.NaN)));
+        checkHasNext(false, g.inject(Double.NaN).is(P.lte(1.0d)));
+        // P.gt
+        checkHasNext(false, g.inject(1.0d).is(P.gt(Double.NaN)));
+        checkHasNext(false, g.inject(Double.NaN).is(P.gt(1.0d)));
+        // P.gte
+        checkHasNext(false, g.inject(1.0d).is(P.gte(Double.NaN)));
+        checkHasNext(false, g.inject(Double.NaN).is(P.gte(1.0d)));
+    }
+
+    /**
+     * Null comparisons. Null is considered to be a distinct type, thus will produce ERROR for comparison against
+     * non-null, just like any cross-type comparison. Null is == null and is != to any non-null (except for NaN, which
+     * is always UNDEF/ERROR).
+     */
+    @Test
+    @FeatureRequirement(featureClass = GraphFeatures.class, feature = GraphFeatures.FEATURE_ORDERABILITY_SEMANTICS)
+    public void testCompareNull() {
+        // null vs. null
+        // P.eq
+        checkHasNext(true, g.inject(null).is(P.eq(null)));
+        // P.neq
+        checkHasNext(false, g.inject(null).is(P.neq(null)));
+        // P.lt
+        checkHasNext(false, g.inject(null).is(P.lt(null)));
+        // P.lte
+        checkHasNext(true, g.inject(null).is(P.lte(null)));
+        // P.gt
+        checkHasNext(false, g.inject(null).is(P.gt(null)));
+        // P.gte
+        checkHasNext(true, g.inject(null).is(P.gte(null)));
+
+        // non-null vs. null
+        // P.eq
+        checkHasNext(false, g.inject(1.0d).is(P.eq(null)));
+        checkHasNext(false, g.inject(null).is(P.eq(1.0d)));
+        // P.neq
+        checkHasNext(true, g.inject(1.0d).is(P.neq(null)));
+        checkHasNext(true, g.inject(null).is(P.neq(1.0d)));
+        // P.lt
+        checkHasNext(false, g.inject(1.0d).is(P.lt(null)));
+        checkHasNext(false, g.inject(null).is(P.lt(1.0d)));
+        // P.lte
+        checkHasNext(false, g.inject(1.0d).is(P.lte(null)));
+        checkHasNext(false, g.inject(null).is(P.lte(1.0d)));
+        // P.gt
+        checkHasNext(false, g.inject(1.0d).is(P.gt(null)));
+        checkHasNext(false, g.inject(null).is(P.gt(1.0d)));
+        // P.gte
+        checkHasNext(false, g.inject(1.0d).is(P.gte(null)));
+        checkHasNext(false, g.inject(null).is(P.gte(1.0d)));
+
+        // NaN vs null
+        // P.eq
+        checkHasNext(false, g.inject(null).is(P.eq(Double.NaN)));
+        checkHasNext(false, g.inject(Double.NaN).is(P.eq(null)));
+        // P.neq
+        checkHasNext(true, g.inject(null).is(P.neq(Double.NaN)));
+        checkHasNext(true, g.inject(Double.NaN).is(P.neq(null)));
+        // P.lt
+        checkHasNext(false, g.inject(null).is(P.lt(Double.NaN)));
+        checkHasNext(false, g.inject(Double.NaN).is(P.lt(null)));
+        // P.lte
+        checkHasNext(false, g.inject(null).is(P.lte(Double.NaN)));
+        checkHasNext(false, g.inject(Double.NaN).is(P.lte(null)));
+        // P.gt
+        checkHasNext(false, g.inject(null).is(P.gt(Double.NaN)));
+        checkHasNext(false, g.inject(Double.NaN).is(P.gt(null)));
+        // P.gte
+        checkHasNext(false, g.inject(null).is(P.gte(Double.NaN)));
+        checkHasNext(false, g.inject(Double.NaN).is(P.gte(null)));
+    }
+
+    /**
+     * Comparisons across type families. FALSE for P.eq, TRUE for P.neq, and UNDEF with binary
+     * reduction to FALSE for P.lt/lte/gt/gte.
+     */
+    @Test
+    @FeatureRequirement(featureClass = GraphFeatures.class, feature = GraphFeatures.FEATURE_ORDERABILITY_SEMANTICS)
+    public void testCompareAcrossTypes() {
+        // P.eq
+        checkHasNext(false, g.inject("foo").is(P.eq(1.0d)));
+        checkHasNext(false, g.inject(1.0d).is(P.eq("foo")));
+        // P.neq
+        checkHasNext(true, g.inject("foo").is(P.neq(1.0d)));
+        checkHasNext(true, g.inject(1.0d).is(P.neq("foo")));
+        // P.lt
+        checkHasNext(false, g.inject("foo").is(P.lt(1.0d)));
+        checkHasNext(false, g.inject(1.0d).is(P.lt("foo")));
+        // P.lte
+        checkHasNext(false, g.inject("foo").is(P.lte(1.0d)));
+        checkHasNext(false, g.inject(1.0d).is(P.lte("foo")));
+        // P.gt
+        checkHasNext(false, g.inject("foo").is(P.gt(1.0d)));
+        checkHasNext(false, g.inject(1.0d).is(P.gt("foo")));
+        // P.gte
+        checkHasNext(false, g.inject("foo").is(P.gte(1.0d)));
+        checkHasNext(false, g.inject(1.0d).is(P.gte("foo")));
+    }
+
+    /**
+     * Over a ternary binary semantics, the AND predicate evaluates to TRUE only if both arguments are TRUE. AND will
+     * propogate ERROR values unless the other argument evalutes to FALSE, since FALSE && X => FALSE.
+     */
+    @Test
+    @FeatureRequirement(featureClass = GraphFeatures.class, feature = GraphFeatures.FEATURE_ORDERABILITY_SEMANTICS)
+    public void testAnd() {
+        final P TRUE = P.eq(1);
+        final P FALSE = P.gt(1);
+        final P ERROR = P.lt(Double.NaN);
+
+        // TRUE, TRUE -> TRUE
+        checkHasNext(true, g.inject(1).and(is(TRUE),is(TRUE)));
+        checkHasNext(true, g.inject(1).is(TRUE.and(TRUE)));
+        // TRUE, FALSE -> FALSE
+        checkHasNext(false, g.inject(1).and(is(TRUE),is(FALSE)));
+        checkHasNext(false, g.inject(1).is(TRUE.and(FALSE)));
+        // TRUE, ERROR -> ERROR -> FALSE
+        checkHasNext(false, g.inject(1).and(is(TRUE),is(ERROR)));
+        checkHasNext(false, g.inject(1).is(TRUE.and(ERROR)));
+
+        // FALSE, TRUE -> FALSE
+        checkHasNext(false, g.inject(1).and(is(FALSE),is(TRUE)));
+        checkHasNext(false, g.inject(1).is(FALSE.and(TRUE)));
+        // FALSE, FALSE -> FALSE
+        checkHasNext(false, g.inject(1).and(is(FALSE),is(FALSE)));
+        checkHasNext(false, g.inject(1).is(FALSE.and(FALSE)));
+        // FALSE, ERROR -> FALSE
+        checkHasNext(false, g.inject(1).and(is(FALSE),is(ERROR)));
+        checkHasNext(false, g.inject(1).is(FALSE.and(ERROR)));
+
+        // ERROR, TRUE -> ERROR -> FALSE
+        checkHasNext(false, g.inject(1).and(is(ERROR),is(TRUE)));
+        checkHasNext(false, g.inject(1).is(ERROR.and(TRUE)));
+        // ERROR, FALSE -> FALSE
+        checkHasNext(false, g.inject(1).and(is(ERROR),is(FALSE)));
+        checkHasNext(false, g.inject(1).is(ERROR.and(FALSE)));
+        // ERROR, ERROR -> ERROR -> FALSE
+        checkHasNext(false, g.inject(1).and(is(ERROR),is(ERROR)));
+        checkHasNext(false, g.inject(1).is(ERROR.and(ERROR)));
+    }
+
+    /**
+     * Being symetrically defined to AND, OR only returns FALSE in the case that both arguments evalute to FALSE.
+     * In cases where one argument is FALSE and the other ERROR, OR propogates the ERROR. Whenever one argument
+     * is TRUE, OR returns TRUE since TRUE || X => TRUE.
+     */
+    @Test
+    @FeatureRequirement(featureClass = GraphFeatures.class, feature = GraphFeatures.FEATURE_ORDERABILITY_SEMANTICS)
+    public void testOr() {
+        final P TRUE = P.eq(1);
+        final P FALSE = P.gt(1);
+        final P ERROR = P.lt(Double.NaN);
+
+        // TRUE, TRUE -> TRUE
+        checkHasNext(true, g.inject(1).or(is(TRUE),is(TRUE)));
+        checkHasNext(true, g.inject(1).is(TRUE.or(TRUE)));
+        // TRUE, FALSE -> TRUE
+        checkHasNext(true, g.inject(1).or(is(TRUE),is(FALSE)));
+        checkHasNext(true, g.inject(1).is(TRUE.or(FALSE)));
+        // TRUE, ERROR -> TRUE
+        checkHasNext(true, g.inject(1).or(is(TRUE),is(ERROR)));
+        checkHasNext(true, g.inject(1).is(TRUE.or(ERROR)));
+
+        // FALSE, TRUE -> TRUE
+        checkHasNext(true, g.inject(1).or(is(FALSE),is(TRUE)));
+        checkHasNext(true, g.inject(1).is(FALSE.or(TRUE)));
+        // FALSE, FALSE -> FALSE
+        checkHasNext(false, g.inject(1).or(is(FALSE),is(FALSE)));
+        checkHasNext(false, g.inject(1).is(FALSE.or(FALSE)));
+        // FALSE, ERROR -> ERROR -> FALSE
+        checkHasNext(false, g.inject(1).or(is(FALSE),is(ERROR)));
+        checkHasNext(false, g.inject(1).is(FALSE.or(ERROR)));
+
+        // ERROR, TRUE -> TRUE
+        checkHasNext(true, g.inject(1).or(is(ERROR),is(TRUE)));
+        checkHasNext(true, g.inject(1).is(ERROR.or(TRUE)));
+        // ERROR, FALSE -> ERROR -> FALSE
+        checkHasNext(false, g.inject(1).or(is(ERROR),is(FALSE)));
+        checkHasNext(false, g.inject(1).is(ERROR.or(FALSE)));
+        // ERROR, ERROR -> ERROR -> FALSE
+        checkHasNext(false, g.inject(1).or(is(ERROR),is(ERROR)));
+        checkHasNext(false, g.inject(1).is(ERROR.or(ERROR)));
+    }
+
+    /**
+     * The NOT predicate inverts TRUE and FALSE but maintains ERROR values. For ERROR, we can neither prove
+     * nor disprove the value expression and hence stick with ERROR.
+     */
+    @Test
+    @FeatureRequirement(featureClass = GraphFeatures.class, feature = GraphFeatures.FEATURE_ORDERABILITY_SEMANTICS)
+    public void testNot() {
+        final P TRUE = P.eq(1);
+        final P FALSE = P.gt(1);
+        final P ERROR = P.lt(Double.NaN);
+
+        // TRUE -> FALSE
+        checkHasNext(false, g.inject(1).not(is(TRUE)));
+        // FALSE -> TRUE
+        checkHasNext(true, g.inject(1).not(is(FALSE)));
+        // ERROR -> ERROR -> FALSE
+        checkHasNext(false, g.inject(1).not(is(ERROR)));
+
+        // Binary reduction with NaN
+        checkHasNext(false, g.inject(1).is(P.eq(Double.NaN)));
+        checkHasNext(true, g.inject(1).is(P.neq(Double.NaN)));
+        checkHasNext(true, g.inject(1).not(is(P.eq(Double.NaN))));
+        checkHasNext(false, g.inject(1).not(not(is(P.eq(Double.NaN)))));
+        checkHasNext(false, g.inject(1).where(__.inject(1).not(is(ERROR))));
+    }
+
+    /**
+     * Ternary XOR semantics.
+     */
+    @Test
+    @FeatureRequirement(featureClass = GraphFeatures.class, feature = GraphFeatures.FEATURE_ORDERABILITY_SEMANTICS)
+    public void testXor() {
+        final P TRUE = P.eq(1);
+        final P FALSE = P.gt(1);
+        final P ERROR = P.lt(Double.NaN);
+
+        final BiFunction<P,P, GraphTraversal> xor = (A, B) -> or(and(is(A),not(is(B))),
+                                                                 and(is(B),not(is(A))));
+
+        // TRUE, TRUE -> FALSE
+        checkHasNext(false, g.inject(1).filter(xor.apply(TRUE, TRUE)));
+        // TRUE, FALSE -> TRUE
+        checkHasNext(true, g.inject(1).filter(xor.apply(TRUE, FALSE)));
+        // TRUE, ERROR -> ERROR -> FALSE
+        checkHasNext(false, g.inject(1).filter(xor.apply(TRUE, ERROR)));
+
+        // FALSE, TRUE -> TRUE
+        checkHasNext(true, g.inject(1).filter(xor.apply(FALSE, TRUE)));
+        // FALSE, FALSE -> FALSE
+        checkHasNext(false, g.inject(1).filter(xor.apply(FALSE, FALSE)));
+        // FALSE, ERROR -> ERROR -> FALSE
+        checkHasNext(false, g.inject(1).filter(xor.apply(FALSE, ERROR)));
+
+        // ERROR, TRUE -> ERROR -> FALSE
+        checkHasNext(false, g.inject(1).filter(xor.apply(ERROR, TRUE)));
+        // ERROR, FALSE -> ERROR -> FALSE
+        checkHasNext(false, g.inject(1).filter(xor.apply(ERROR, FALSE)));
+        // ERROR, ERROR -> ERROR -> FALSE
+        checkHasNext(false, g.inject(1).filter(xor.apply(ERROR, ERROR)));
+    }
+
+}
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java
index 6cfe51a..a4818f7 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java
@@ -128,15 +128,18 @@ public final class TinkerGraphStep<S, E extends Element> extends GraphStep<S, E>
 
     private <E extends Element> Iterator<E> iteratorList(final Iterator<E> iterator) {
         final List<E> list = new ArrayList<>();
-        while (iterator.hasNext()) {
-            final E e = iterator.next();
-            if (HasContainer.testAll(e, this.hasContainers))
-                list.add(e);
-        }
 
-        // close the old iterator to release resources since we are returning a new iterator (over list)
-        // out of this function.
-        CloseableIterator.closeIterator(iterator);
+        try {
+            while (iterator.hasNext()) {
+                final E e = iterator.next();
+                if (HasContainer.testAll(e, this.hasContainers))
+                    list.add(e);
+            }
+        } finally {
+            // close the old iterator to release resources since we are returning a new iterator (over list)
+            // out of this function.
+            CloseableIterator.closeIterator(iterator);
+        }
 
         return new TinkerGraphIterator<>(list.iterator());
     }