Strict Relational Operators

felix felix8a at gmail.com
Wed Apr 12 23:48:51 UTC 2017


One common JS problem is NaNs ending up in unexpected places, and it's
often difficult to trace back where they came from. Getting a type
error instead of a NaN would be nice.

I think this is a reasonable argument for being able to write
expressions with non-coercing operators, and this is why I'd lean
toward annotating an entire expression as non-coercing, instead of
doubling the number of operators.

On Wed, Apr 12, 2017 at 9:25 AM, T.J. Crowder
<tj.crowder at farsightsoftware.com> wrote:
> FWIW, I think the next steps for this discussion are:
>
> 1. To hear from people whether they feel the need for these operations in
> their everyday work. It's interesting, you so often hear people saying
> "Always use `===`, not `==`!" with...fervor...but apparently strict versions
> of the relational operators aren't (or weren't) on people's minds. :-)
>
> 2. To hear from implementers about the difficulty level of adding four more
> symbolic operators (`<=<`, `<==`, `>=>`, and `>==` or whatever they end up
> being).
>
> (I like my non-symbolic operators -- `lt`, `lte`, and such -- but I doubt
> they'd pass muster, for the reasons Brendan flagged up in the thread about
> infix functions.)
>
> If the answer to #1 is "meh," discussions of operators vs. functions is
> moot; nothing's going to happen. If the answer to #1 is "Oh yes, this would
> be really useful" and the answer to #2 is "Fairly straightforward", that's a
> solid steer as well, as is a "Actually, surprisingly hard" to #2 (and it
> would be surprising, at least to me, but what do I know about implementing a
> JavaScript engine).
>
> -- T.J. Crowder
>
> On Wed, Apr 12, 2017 at 4:49 PM, Michael J. Ryan <tracker1 at gmail.com> wrote:
>>
>> That's part of why I suggested it... My mention of Object.* Was mainly
>> that it could defer to a common base class/constructor implementation for
>> comparison.  And that a string and number implementation should be
>> provided...
>>
>> I'm also good with having non-matching types return undefined while
>> matching types is a Boolean.
>>
>> Object.* could just defer to the prototype implementation of the first
>> value.. null or undefined always returning undefined.
>>
>> --
>> Michael J. Ryan - tracker1 at gmail.com - http://tracker1.info
>>
>> Please excuse grammar errors and typos, as this message was sent from my
>> phone.
>>
>> On Apr 12, 2017 7:04 AM, "Darien Valentine" <valentinium at gmail.com> wrote:
>>>
>>> > Personally I think `a < b` should just become a compile error if the
>>> > types are not number.
>>>
>>> Breaking stuff aside, I think this is an important point. The fact that
>>> the LT/GT operators do work on strings is a source of bugs. As with default
>>> sort, I’ve seen code written a number of times where it was evident the
>>> author expected the behavior would be more like
>>> `Intl.Collator.prototype.compare`.
>>>
>>> Unless I’m missing some important common use case for comparing strings
>>> based on byte values (`assert('a' > 'B')`), I think `Number.gt`,
>>> `Number.gte`, `Number.lt`, `Number.lte` would be a good solution.
>>>
>>> On Wed, Apr 12, 2017 at 5:09 AM, T.J. Crowder
>>> <tj.crowder at farsightsoftware.com> wrote:
>>>>
>>>> > Personally I think `a < b` should just become a compile error if the
>>>> > types are not number. TypeScript?
>>>>
>>>> I'm not following you. JavaScript variables don't have types, so it
>>>> can't become a compile-time error; and making it one would *massively* break
>>>> the web. (Yes, you can use TypeScript to get types if you like, but we're
>>>> not talking about TypeScript.)
>>>>
>>>> > ...that's a separable concern which should not be part of the
>>>> > operator's behaviour IMO...
>>>>
>>>> There's no talk of changing how `<` and `>` (or `<=` and `>=`) work.
>>>>
>>>> But just as we have `==` (loose, coercing) and `===` (strict,
>>>> non-coercing), the discussion is about having strict non-coercing versions
>>>> of `<`, `>`, `<=`, and `>=`.
>>>>
>>>> -- T.J. Crowder
>>>>
>>>>
>>>> On Wed, Apr 12, 2017 at 10:00 AM, Alexander Jones <alex at weej.com> wrote:
>>>>>
>>>>> Personally I think `a < b` should just become a compile error if the
>>>>> types are not number. TypeScript?
>>>>>
>>>>> If you want to also check that they are both number (that's surely what
>>>>> we mean here and not that they are both string!) and return `false` if not,
>>>>> that's a separable concern which should not be part of the operator's
>>>>> behaviour IMO. It would appear to just mask fundamental typing errors,
>>>>> unless I am missing some perspective?
>>>>>
>>>>> Alex
>>>>>
>>>>> On Wed, 12 Apr 2017 at 09:02, T.J. Crowder
>>>>> <tj.crowder at farsightsoftware.com> wrote:
>>>>>>
>>>>>> Grr, there's always something. I forgot to mention that solving this
>>>>>> with functions is an option because short-circuiting isn't an issue, both
>>>>>> operands have to be evaluated by these relational operators anyway. So
>>>>>> unlike the motiviations for infix functions or macros or whatever, we don't
>>>>>> have that issue here.
>>>>>>
>>>>>> -- T.J. Crowder
>>>>>>
>>>>>>
>>>>>> On Wed, Apr 12, 2017 at 8:56 AM, T.J. Crowder
>>>>>> <tj.crowder at farsightsoftware.com> wrote:
>>>>>>>
>>>>>>> Very interesting stuff so far.
>>>>>>>
>>>>>>> My take on some options, organized into sections:
>>>>>>>
>>>>>>> * Solving it in userland
>>>>>>> * Using symbolic operators
>>>>>>> * Using functions
>>>>>>> * Using non-symbolic operators
>>>>>>>
>>>>>>> # Solving it in userland:
>>>>>>>
>>>>>>> Anyone wanting strict relational operators today can readily give
>>>>>>> themselves functions for it:
>>>>>>>
>>>>>>> ```js
>>>>>>> const lt = (a, b) => typeof a === typeof b && a < b;
>>>>>>> ```
>>>>>>>
>>>>>>> Usage:
>>>>>>>
>>>>>>> ```js
>>>>>>> if (lt(a, b)) {
>>>>>>>     // ...
>>>>>>> }
>>>>>>> ```
>>>>>>>
>>>>>>> So the question is whether the value of having a standard way of
>>>>>>> expressing this is worth the cost of adding it?
>>>>>>>
>>>>>>> Playing into that is that various options come with varying costs:
>>>>>>>
>>>>>>> * Using symbolic operators has a cost, but doesn't change
>>>>>>> fundamentals; I'm not an implementer, but I'd think the cost would be fairly
>>>>>>> low (but non-zero). *Any* syntax change rattles parsing cages everywhere,
>>>>>>> but syntax changes are now fairly regular occurrences in JavaScript.
>>>>>>> * Using functions means no new syntax, which means not rattling
>>>>>>> parsing cages, and are polyfillable.
>>>>>>> * Using non-symbolic operators rattles cages, and probably more
>>>>>>> significantly than new symbolic ones, and has been rejected in the past
>>>>>>> (`is`/`isnt`).
>>>>>>>
>>>>>>> So that's in the mix.
>>>>>>>
>>>>>>> # Using symbolic operators:
>>>>>>>
>>>>>>> ## Form
>>>>>>>
>>>>>>> The closest I can come to consistency with `==`/`===` and `!=`/`!==`
>>>>>>> is:
>>>>>>>
>>>>>>>     Loose    Strict
>>>>>>>      ==       ===
>>>>>>>      !=       !==
>>>>>>>      <        <=<
>>>>>>>      >        >=>
>>>>>>>      <=       <==
>>>>>>>      >=       >==
>>>>>>>
>>>>>>> We can think of the `=` in the middle as being what signifies the
>>>>>>> strict type aspect. The second `<` and `>` on `<=<` and `>=>` are a hack,
>>>>>>> but a reasonable hack that's in the spirit of the original two strict
>>>>>>> operators. :-)
>>>>>>>
>>>>>>> ## Semantics
>>>>>>>
>>>>>>> Because they're like `!==` and `===`, their semantics would have to
>>>>>>> be in line with `!==` and `===`: The result is `true` if the operands are of
>>>>>>> the same type and the relation is true, `false` otherwise.
>>>>>>>
>>>>>>> # Using functions:
>>>>>>>
>>>>>>> ## Form
>>>>>>>
>>>>>>> Given `Object.is(value1, value2)` there's an argument for putting
>>>>>>> these on `Object` as well. But `Object` is an odd place for them (and indeed
>>>>>>> for `is`). Perhaps we need a place for these to go. But sticking with
>>>>>>> `Object` for now:
>>>>>>>
>>>>>>> ```js
>>>>>>> Object.lt(value1, value2)
>>>>>>> Object.gt(value1, value2)
>>>>>>> Object.lte(value1, value2)
>>>>>>> Object.gte(value1, value2)
>>>>>>> ```
>>>>>>>
>>>>>>> So:
>>>>>>>
>>>>>>> ```js
>>>>>>> if (Object.lt(a, b)) {
>>>>>>>     // ...
>>>>>>> }
>>>>>>> ```
>>>>>>>
>>>>>>> Verbose, and again, using `Object` if I'm comparing numbers or
>>>>>>> strings seems wrong. But it's consistent with the prior practice of
>>>>>>> `Object.is`.
>>>>>>>
>>>>>>> Michael J. Ryan suggested putting them on `Number`, `String`, and
>>>>>>> `Object` instead, on the theory that if you're being strict, you know what
>>>>>>> you're comparing. I'm not sure I agree that you do (a generic "take the
>>>>>>> lower of these two" function, for instance), but there's something there. It
>>>>>>> doesn't address the verbosity issue. (Presumably we only need `Number` and
>>>>>>> `String` though, unless we're going to introduce a whole mechanism for
>>>>>>> relational comparison of objects. Or unless the `Object` version just hands
>>>>>>> off to the `Number` or `String` version based on the first operand type.)
>>>>>>>
>>>>>>> ## Semantics
>>>>>>>
>>>>>>> Using functions gives us the opportunity to use slightly different
>>>>>>> semantics:
>>>>>>>
>>>>>>> 1. `true`: The operands are the same type and the relation is true
>>>>>>> 2. `false`: The operands are the same type and the relation is false
>>>>>>> 3. `undefined`: The operands are of different types
>>>>>>>
>>>>>>> This takes advantage of the fact `undefined` is falsy to not get in
>>>>>>> the way of people just using the result in a condition, but if they examine
>>>>>>> the result itself, it's possible to differentiate between #2 and #3.
>>>>>>>
>>>>>>> Sadly, `Object.is` (the exposed version of the SameValue algorithm)
>>>>>>> does not make this distinction.
>>>>>>>
>>>>>>> # Non-symbolic operators
>>>>>>>
>>>>>>> JavaScript already has at least one binary operator that isn't
>>>>>>> symbolic: `in`. Maybe there's a case for adding more. Brendan Eich is [on
>>>>>>> record](https://esdiscuss.org/topic/suggestion-mapping-symbolic-infix-ops-to-binary-functions#content-5)
>>>>>>> five years ago as having issues with them:
>>>>>>>
>>>>>>> > > modulo, div, divmod, has, extends
>>>>>>>
>>>>>>> > These are much better as methods. Polyfillable, but also not
>>>>>>> > subject to weird line terminator restrictions on the left. Same arguments
>>>>>>> > killed is/isnt.
>>>>>>>
>>>>>>> Hence `Object.is`, presumably (the linked discussion was about infix
>>>>>>> functions, not `is`). I don't know if that view has shifted in the
>>>>>>> subsequent five years; there have been big changes in the way JavaScript
>>>>>>> moves forward. But that was an objection at least then.
>>>>>>>
>>>>>>> ## Form
>>>>>>>
>>>>>>> `lt`, `lte`, `gt`, and `gte`. And while we're at it, `eq` and
>>>>>>> `noteq`. So:
>>>>>>>
>>>>>>> ```js
>>>>>>> if (a lt b) {
>>>>>>>     // ...
>>>>>>> }
>>>>>>> ```
>>>>>>>
>>>>>>> To avoid breaking the web, the new non-symbolic operators would have
>>>>>>> to remain valid identifiers, only being operators by context, a bit like how
>>>>>>> `for` can be a literal property name (`obj.for`) as of ES5 because we know
>>>>>>> from context that it's not the `for` statement. But I assume (not being a
>>>>>>> parser guy) that it's more complex to handle the above (those "weird line
>>>>>>> terminator conditions on the left" Eich mentioned).
>>>>>>>
>>>>>>> ## Semantics
>>>>>>>
>>>>>>> Like functions, non-symbolic operators let us consider slightly
>>>>>>> tweaking the semantics, e.g. that `undefined` result for operands of
>>>>>>> different types I mentioned earlier.
>>>>>>>
>>>>>>> # Wrap-up thoughts
>>>>>>>
>>>>>>> Unless it's left to userland, the simplest, least cage-rattling
>>>>>>> approach is to add functions to `Object`, but it's also ugly to use. It does
>>>>>>> have the benefit (in my view) of letting us tweak the return value when the
>>>>>>> types don't match.
>>>>>>>
>>>>>>> It seems to me the second least cage-rattling is to add symbolic
>>>>>>> operators consistent with `===` and `!==`.
>>>>>>>
>>>>>>> -- T.J. Crowder
>>>>>>>
>>>>>>> On Wed, Apr 12, 2017 at 7:05 AM, Michael J. Ryan <tracker1 at gmail.com>
>>>>>>> wrote:
>>>>>>>>
>>>>>>>> Thinking on it... (Number|Object|String)
>>>>>>>> .strict(Equal|Greater|Less...) Methods (a, b) might be better...  If either
>>>>>>>> value isn't a match for the bound type, it's a false, even if both sides are
>>>>>>>> equal...
>>>>>>>>
>>>>>>>> Ex,.
>>>>>>>>
>>>>>>>> Number.strictEqual(null, null)  false
>>>>>>>>
>>>>>>>> Object.strictEqual(1, 1)  false
>>>>>>>> ...
>>>>>>>>
>>>>>>>> If you're doing a strict compare, one can presume you should know
>>>>>>>> what you're comparing.
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> Michael J. Ryan - tracker1 at gmail.com - http://tracker1.info
>>>>>>>>
>>>>>>>> Please excuse grammar errors and typos, as this message was sent
>>>>>>>> from my phone.
>>>>>>>>
>>>>>>>> On Apr 11, 2017 10:46 PM, "felix" <felix8a at gmail.com> wrote:
>>>>>>>>>
>>>>>>>>> Maybe every operator can have a non-coercing variant?
>>>>>>>>>
>>>>>>>>> One possible syntax is to have a modifier on operators
>>>>>>>>>     x = a (<) b (+) c (&&) (!)d;
>>>>>>>>>     if (x (!=) y) ...
>>>>>>>>>
>>>>>>>>> Another possible syntax is to have a modifier on expressions
>>>>>>>>>     x = #(a < b + c && !d)
>>>>>>>>>     if #(x != y) ...
>>>>>>>>>
>>>>>>>>> On Tue, Apr 11, 2017 at 7:48 PM, Darien Valentine
>>>>>>>>> <valentinium at gmail.com> wrote:
>>>>>>>>> > Although I’m unsure if this is wise given there are already
>>>>>>>>> > eleven symbols
>>>>>>>>> > that are combinations of `=` and `<`/`>`, for symmetry with `==`
>>>>>>>>> > and `===`
>>>>>>>>> > I’d imagine something like this:
>>>>>>>>> >
>>>>>>>>> > ```
>>>>>>>>> > COERCIVE  STRICT
>>>>>>>>> >>         =>=
>>>>>>>>> > <         =<=
>>>>>>>>> >>=        =>==
>>>>>>>>> > <=        =<==
>>>>>>>>> > ```
>>>>>>>>> >
>>>>>>>>> > Could also follow the pattern `>==` (strict GT) and `<===`
>>>>>>>>> > (strict GTE),
>>>>>>>>> > which avoids the awkwardness of the latter two sharing opening
>>>>>>>>> > chars with
>>>>>>>>> > `=>`, but that seems more ambiguous since `>==` doesn’t let you
>>>>>>>>> > infer
>>>>>>>>> > whether it means strict GT or strict GTE.
>>>>>>>>> >
>>>>>>>>> > It’d be nice to have this functionality built in, but I wonder if
>>>>>>>>> > it’d
>>>>>>>>> > possibly be preferable to provide it through methods of one of
>>>>>>>>> > the built-in
>>>>>>>>> > objects, rather than as operators. Functions after all are more
>>>>>>>>> > flexible.
>>>>>>>>> >
>>>>>>>>> > _______________________________________________
>>>>>>>>> > es-discuss mailing list
>>>>>>>>> > es-discuss at mozilla.org
>>>>>>>>> > https://mail.mozilla.org/listinfo/es-discuss
>>>>>>>>> >
>>>>>>>>> _______________________________________________
>>>>>>>>> es-discuss mailing list
>>>>>>>>> es-discuss at mozilla.org
>>>>>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>>>>>
>>>>>>>>
>>>>>>>> _______________________________________________
>>>>>>>> es-discuss mailing list
>>>>>>>> es-discuss at mozilla.org
>>>>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>>>>>
>>>>>>>
>>>>>>
>>>>>> _______________________________________________
>>>>>> es-discuss mailing list
>>>>>> es-discuss at mozilla.org
>>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>
>>>>
>>>
>>>
>>> _______________________________________________
>>> es-discuss mailing list
>>> es-discuss at mozilla.org
>>> https://mail.mozilla.org/listinfo/es-discuss
>>>
>>
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
>>
>
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>


More information about the es-discuss mailing list