docs: public static Boolean isEqual(Object left, Object right, EvaluationContext ctx ) {
          if ( left == null || right == null ) {
              return left == right;
          }
  
          // spec defines that "a=[a]", i.e., singleton collections should be treated as the single element
          // and vice-versa
          if( left instanceof Collection && !(right instanceof Collection) && ((Collection)left).size() == 1 ) {
              left = ((Collection)left).toArray()[0];
          } else if( right instanceof Collection && !(left instanceof Collection) && ((Collection)right).size()==1 ) {
              right = ((Collection) right).toArray()[0];
          }
  
          if( left instanceof Range && right instanceof Range ) {
              return isEqual( (Range)left, (Range) right );
          } else if( left instanceof Iterable && right instanceof Iterable ) {
              return isEqual( (Iterable)left, (Iterable) right );
          } else if( left instanceof Map && right instanceof Map ) {
              return isEqual( (Map)left, (Map) right );
          } else if (left instanceof TemporalAccessor && right instanceof TemporalAccessor) {
              // Handle specific cases when both date / datetime
              if (BuiltInType.determineTypeFromInstance(left) == BuiltInType.TIME && BuiltInType.determineTypeFromInstance(right) == BuiltInType.TIME) {
                  return isEqualTime((TemporalAccessor) left, (TemporalAccessor) right);
              } else if (BuiltInType.determineTypeFromInstance(left) == BuiltInType.DATE_TIME && BuiltInType.determineTypeFromInstance(right) == BuiltInType.DATE_TIME) {
                  return isEqualDateTime((TemporalAccessor) left, (TemporalAccessor) right);
              } // fallback; continue:
          }
          return compare( left, right, ctx, (l, r) -> l.compareTo( r ) == 0  );
      }