Skip to content
This repository has been archived by the owner on Jan 28, 2023. It is now read-only.

Replace with keyword "exists" for clarity? #33

Closed
selipso opened this issue Dec 4, 2018 · 16 comments
Closed

Replace with keyword "exists" for clarity? #33

selipso opened this issue Dec 4, 2018 · 16 comments

Comments

@selipso
Copy link

selipso commented Dec 4, 2018

The discussion in #17 gets to the heart of the confusion and use case of this operator. The opening statement and this comment #17 (comment) present both sides of the case really well. The operator's narrow purpose make it hard to understand its use case to a beginner. To add more clarity, maybe it would be better to just use a keyword: "exists". Highly readable and can work easily within js.

const callback  = ( response ) => {
  if (response exists) {
    next();
  } else {
    handleError();
  }
}

or an equally readable ternary version:

const callback = ( response ) => {
  response exists ? next() : handleError()
}

To execute a statement after the operator one can just continue using the regular || syntax.

let status = null;
const defaultStatusMessage = 'No status set'
function getStatus() {
  return status exists || defaultStatusMessage;
}
function setStatus( newStatus ) {
  status = newStatus;
  return status exists || defaultStatusMessage;
}

console.log(getStatus()); // 'No status yet'
setStatus('');
console.log(getStatus()); // empty string

@alangpierce pointed out that this is a unary operator instead of a binary operator. In reality, it would function as both. Here are a few test cases to make this behavior clearer. In terms of the implementation, the unary version, exists, can either evaluate to false or the value directly before the operator. It's not a fully boolean return value like ||. Another clarification is that exists || is a combination operator similar to else if. It behaves slightly differently when used in conjunction than either one individually for enhanced readability. So in reality, there is both a unary version and a binary version.

I realize the binary version can be confusing because of using || so there can be a different keyword or operator for it too. The prior art for this in Swift biases us to think that it has to be a binary operator but Swift is a statically / strongly typed language whereas with JavaScript we can have a perfectly functional and useful unary version.

const posiBool = true;
const negaBool = false;
const emptyString = '';
const nullishValue = null;
const zeroValue = 0;
const myString = 'Hello';
const myNumber = 42;

console.log(posiBool exists || 'There is nothing true in this world');  // true
console.log(negaBool exists || 'This edge case is different'); // 'This edge case is different'
console.log(emptyString exists || 'Showing empty status'); // ''
console.log(emptyString exists); // '' => unary operator, same result
console.log(zeroValue exists); // 0
console.log(nullishValue exists); // false
console.log(nullishValue exists || 'short circuit evaluation'); // 'short circuit evaluation'
console.log(myString exists); // 'Hello'
console.log(myNumber exists); // 42
console.log(zeroValue exists || myNumber); // 0
console.log(emptyString exists || myString); // ''
console.log(floopityFlop exists || nullishValue exists || zeroValue exists); // 0 since floopityFlop is undefined
@hax
Copy link
Member

hax commented Dec 4, 2018

[deleted because the author have edited original post]

@lazarljubenovic
Copy link

I really dislike a postfix keyword operator.

@ljharb
Copy link
Member

ljharb commented Dec 4, 2018

Would the same keyword work in any other context?

@alangpierce
Copy link

To be clear, this is a different operator that you're proposing (not just a different syntax). Nullish coalescing is a binary operator, and you're proposing a postfix unary operator.

I also think this operator just doesn't work for the use cases that nullish coalescing is trying to solve. In the getStatus example you gave, if status exists evaluates to true, then getStatus would return true, which is wrong. If it evaluates to '' (which I'd find very surprising), then getStatus would return 'No status set', which is wrong. Unless you really want to mess with how JS evaluation works, I don't think there's a way to make it return the expected return value of '' (which is what status ?? 'No status set' would do).

CoffeeScript does use ? as a postfix operator for != null, and it is the same token that they use for their version of nullish coalescing, but I think it's really a different use case and a pretty minor improvement over != null.

@selipso
Copy link
Author

selipso commented Dec 4, 2018

@alangpierce Appreciate the feedback. I admit my statusMessage example originally was half-baked and put together at 1 am. Updated the original post with more specific and clarifying examples to address the comments so far.

One thing I found as I was writing the rudimentary test case examples with console.log above is that the behavior for a false value is the one edge case that has less intuitive functionality than the currently proposed ?? operator. For example:

console.log(negaBool exists); // false versus console.log(negaBool exists || 'some default value');. The second statement intuitively would evaluate to 'some default value'. The exists || operator can be customized so that it evaluates to whichever value fits the intended outcome or use case better.

@Mouvedia
Copy link

Mouvedia commented Dec 4, 2018

null has sometimes an explicit meaning so the keyword exists would be inappropriate. null in javascript often means explicitly non-provided.

e.g.

  • an address without a borough would have its borough property set to null (let's forget about Alaska for the sake of the example)
  • a boolean answer could have true/false/null as valid values where null means skipped and undefined he didn't pass the question yet

@hax
Copy link
Member

hax commented Dec 5, 2018

@selipso

Another clarification is that exists || is a combination operator similar to else if. It behaves slightly differently when used in conjunction than either one individually for enhanced readability. So in reality, there is both a unary version and a binary version.

So your x exists || y is just same as x ?? y. But you introduce an extra operator x exists which make (x exists) || y === x exists || y would be broken if x is non nullish but falsy.

@selipso
Copy link
Author

selipso commented Dec 5, 2018

@hax yes that is right. The above equality would not hold true for non-nullish falsy values and you would have to place parentheses to change the order, like you did, and like we often already do with other complex boolean evaluation chains.

@hax
Copy link
Member

hax commented Dec 5, 2018

we often already do with other complex boolean evaluation chains.

We do it for not relying on operator precedences which is hard to remember. But it's not the case for exists.

It's very weird to understand x exists || y not same as (x exists) || y if exists could be a postfix operator. And you are asking programmers treat it like (x) (exists ||) (y) (actually not a valid syntax, but you know what I mean).

So why not x existsor y? Why not x ifnullthen y? Why not just x ?? y?

@selipso
Copy link
Author

selipso commented Dec 6, 2018

Because readability and clarity. We write code for other people who may or may not be the same skill level as us or used to programming in the same language as us. But most commonly-used programming languages (except TeX) are written in English. That is a reality of our profession. Whether you agree or not, I hope you see that this comes from a place of empathy. I think a trade-off of an edge case of falsy non-null values where one must add two parentheses is worth making if it means we aggregately save budding programmers even 2 minutes of time in having to look up and understand a new, confusing, potentially unnecessary operator with little precedence. Those are not my beliefs but they are well justified.

Multiply the above time savings by the millions of new programmers who will be learning JavaScript in the coming decades and the trade-off in my mind heavily favors readability without a shadow of a doubt.

@dannyskoog
Copy link

Given the former speaker, personally I believe that there would be a lot of already existing syntax in need of renaming if the goal is to have them human language based. So obviously that has not been top priority previously and shouldn’t be now either IMO.

I’ve had the benefit of already utilizing the proposed syntax (??) as part of C# programming for a few years. I was very happy with it, but probably even happier when I saw the same syntax proposed for EcmaScript :)

My point is that syntax in general, to me, already are quite abstract. But that’s fine, since you have to learn them anyway. Although turning them concrete (read human language based) just for the sake of it will make them lose other capabilities

@lazarljubenovic
Copy link

As others explained above, exists doesn't work the behavior of ??. I used to have a utility function which mimics the ?? and used to call it fallback, but that doesn't work for the infix operator. If we go with a keyword, I think we should think along the lines of a otherwise b or a orelse b. But no matter how I phrase it, it still has a boolean-y feel to it.

Then again, if it's a keyword, why not just make it a global function? a ?? b could easily be nullish(a, b) or whatever name is chosen. In fact, I used to have a utility in my code called fallback (wanted default but thought, better not use a keyword).

Nothing is clearer than a function call: the order of execution is well-defined, there are no gotchas with operator priority, etc. But it's so. damn. long!

The whole idea of having symbols for operators is that they are common enough and are used often. It turns out that humans are pretty great at remembering symbols and understanding them in context, without a full explanation repeated each time. Just think about it: not just in code (;, ?, :, +, -, %, @, !, {, }, [, ], (, ), \, , :, *, **), but also in other sciences, like chemistry H₂O. If a symbol is not enough, we tend to extend it: = . While some of these symbols might look crazy to you, they are perfectly understandable (and very useful) for people who use them every day: they don't want to keep writing the full word all the time. It adds clutter. We even invent more symbols: . Or use special positioning of existing ones to change its meaning: ². We borrow from non-latin alphabets too: Δ, Σ, Π. Think of all the brands you recognize by their icon. Japanese is another example that symbols just work.

I think that ?? fits pretty well. It still has a bit of a "truthy" feeling to it because of ? in a ? b : c, but it certainly feels less truthy than |||. But since the operator is basically asking a question and deciding what to return based on the operands, having a question before ?? and an answer after it feel like a pretty understandable structure and not counter-intuitive at all. Sure, you cannot guess its meaning 100% without looking it up somewhere, but then again you can say that for pretty much every part of every language.

@zenparsing
Copy link
Member

@lazarljubenovic Great points, and also a great argument for reusing symbols from other nearby languages.

@selipso
Copy link
Author

selipso commented Dec 6, 2018

I really like the idea of using otherwise or a global function. I don't intend to make this a symbol vs keywords debate, but the case against symbols is pretty strong with this particular operator because of three main reasons:

  1. Confusion with optional chaining (?.) syntax
  2. The more commonly used boolean operators also use two symbols in js (&& and ||). Combine with == and === comparison operators, along with their opposites != and !==, and we approach the limits of memory chunking in the human brain. So in this case, one additional "double symbol" operator really is a detriment to remembering them all.
  3. Other issues that have been raised (linked in my previous comment) that already acknowledge the confusing nature of this operator.

Additionally, I am strongly against the argument that "we should use this symbol because all other languages with this feature (and there aren't many) use this symbol". That line of thinking is the opposite of innovation, progress, and independent thinking. For example, Python uses is None for a non-existence check. I find that to be highly readable, innovative, and easy to use.

@hax
Copy link
Member

hax commented Dec 10, 2018

The rationale is not "we should use this symbol because all other languages with this feature (and there aren't many) use this symbol".

The rationale is "we should not invent a totally different surface syntax without very strong reason if other languages with this feature already have the solutions".

@ljharb
Copy link
Member

ljharb commented Jan 28, 2023

Closing, since this proposal is at stage 4.

@ljharb ljharb closed this as completed Jan 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants