Optional chaining syntax but with the "mice" operator ?

# Andrea Giammarchi (2 months ago)

This is basically a solution to a common problem we have these days, where modules published in the wild might have a default property, to support ESM logic, or not.

// current optional chaining logic
const imported = exported?.default ?? exported;

// my "mice operator" proposal
const imported = exported<?.default;

Semantically speaking, not only <? actually looks like a mice, it also points at its previous value in case the chaining didn't work.

Beside the basic example, the "mice operator" might save CPU cycles when it comes to involving more complex expressions, i.e.

// current "solution"
const thing = require('thing')?.default ?? require('thing');

// mice operator
const thing = require('thing')<?.default;

This is also easily tranpilable, so kinda a no-brainer for modern dev tools to bring in.

TL;DR specially for cases where an accessed property should fallback to its source, this operator might save both typing and CPU time whenever it's needed.

What do you think about it?

# Tab Atkins Jr. (2 months ago)

On Thu, Sep 5, 2019 at 2:39 PM Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

This is basically a solution to a common problem we have these days, where modules published in the wild might have a default property, to support ESM logic, or not.

// current optional chaining logic
const imported = exported?.default ?? exported;

// my "mice operator" proposal
const imported = exported<?.default;

Semantically speaking, not only <? actually looks like a mice, it also points at its previous value in case the chaining didn't work.

Beside the basic example, the "mice operator" might save CPU cycles when it comes to involving more complex expressions, i.e.

// current "solution"
const thing = require('thing')?.default ?? require('thing');

// mice operator
const thing = require('thing')<?.default;

This is also easily tranpilable, so kinda a no-brainer for modern dev tools to bring in.

TL;DR specially for cases where an accessed property should fallback to its source, this operator might save both typing and CPU time whenever it's needed.

I find it a rather curious pattern, that I'd never seen before! Is it used in anything besides this ESM-compat thing you're talking about?

(Saving CPU cycles is not a convincing argument; it's trivial to write such a line over two declarations and avoid any expensive recomputations.)

# Andrea Giammarchi (2 months ago)

Another use case that I believe will be common is the following one:

// current state of the art
const result = dbQuery(data)?.rows ?? 'did it just failed or what?';

// VS the "mice operator"
const result = dbQuery(data)<?.rows;

// if it was rows
if (Array.isArray(result))
  console.log(result);
else if (result instanceof Error)
  console.error(result.message);
else
  console.warn(`unexpected result: ${result}`);

Ideally, the "mice" should grant chaining up to its latest presence, but I wouldn't know right now how to reference to it ...

// if no ?? is needed, this might work
const result = dbQuery(data)<?.rows?.data?.entry;

// if ?? is needed, no idea how to back-reference the latest successfull
"mice" result
# Michael Luder-Rosefield (2 months ago)

Another pattern it could be useful in is with, say, nosql dbs where something might be an object or id reference:

const fooId = foo<?.id;
# Andrea Giammarchi (2 months ago)

absolutely, I'm working with PostgreSQL these days and indeed for any promise/awaited result this pattern looks like a win, and while it's targeting a limitation of the chaining one, it can be used in various other cases where knowing the initial result is more important than just falling back to "dunnoWhatHappenedThere"

# Andrea Giammarchi (2 months ago)

Since somebody asked me already elsewhere about the expected precedence of the operator, this would be my answer:

const result = await dbQuery(data)<?.rows;

would be the equivalent of

let result = await dbQuery(data);
if (result != null && result.rows != null)
  result = result.rows;

or to better answer about precedence:

const result = (await dbQuery(data))<?.rows;

I hope this adds some extra context to this proposal.

# Andrea Giammarchi (2 months ago)

As someone noticed already, the operator should be called eventually "mouse" operator, as mice is plural and was a misunderstand of mine 😅

# Claude Pache (2 months ago)

Le 5 sept. 2019 à 23:39, Andrea Giammarchi <andrea.giammarchi at gmail.com> a écrit :

This is basically a solution to a common problem we have these days, where modules published in the wild might have a default property, to support ESM logic, or not.

// current optional chaining logic
const imported = exported?.default ?? exported;

// my "mice operator" proposal
const imported = exported<?.default;

The semantics of the ?. in exported?.default is not to check whether the default property exists, but whether exported evaluates to undefined or null. If the default property is absent, exported.default has always evaluated to undefined, and there is no need to optional chaining operator. So that I guess you actually meant:

``js const imported = exported.default ?? exported;

# Naveen Chawla (2 months ago)

I think introducing this operator encourages bad logic design like "instanceof", isArray etc. These are unreadable disambiguation factors in that they don't inform about which part the expression is going to the next stage in the process. Also it leads to "type branching", which tends towards more convoluted logical flow. These things, in my mind, would lead to more bugs. Hence I would tend to be against introducing it, especially in light of other proposals that I find more useful that haven't been taken even to discussion.

# Andrea Giammarchi (2 months ago)

The purpose is to address what chaining lacks, in terms of "stopping" at some point whenever it's available or not.

Take this example:

// the current chaining operator
const result = nmsp.some.payload()?.result ?? nmsp.some.payload();

// the mouse operator
const result = nmsp.some.payload()<?.result;

Keeping it semantic, the mouse operator is "a trap" for the chain that makes reading possible expensive parts of the chain easier. An utility to obtain the same code would look something like the following:

// the mouse utility
const mouse = trap => {
  // a way to self clean right after, as it is for RegExp.$* values
  // which is something impossible to obtain with regular syntax
  Promise.resolve(mouse.trap = trap).then(() => delete mouse.trap);
  return trap;
};

// the previous example
const result = mouse(nmsp.some.payload())?.result ?? mouse.trap;

Since there is no easy way to syntactically obtain the same with the current ? and ?? without repeating all steps in the right side of the ??, I've thought this "mouse operator" would play a role to actually avoid bugs easily introduced by repeating calls on the right hand side of the ?? either via getters or expensive operations.

It is also possible to keep going on a chain or eventually provide feedbacks of what went wrong:

const name = await some.query(id)<?.rows?.[0]?.name;
if (typeof name !== 'string')
  throw name;

As summary, considering how semantic it's the operator in both visual and practical meanings, and considering it's not possible to achieve the same result through ??, I wish it would be considered as complementary help for the recently introduced ?. and ?? syntax.

So thanks in advance for possible consideration.

# Felipe Nascimento de Moura (2 months ago)

Doesn't that bring risks to breaking the web?

You seen, many, MANY servers running php have the "shot-tags" feature enabled, in which pages with <? and ?> will be interpreted.

In this case, any html page with embedded scripts using this operator, or event .js files when the server is configured to also run php in them, will break.

Or am I missing something here?

[ ]s

--

Felipe N. Moura Web Developer, Google Developer Expert developers.google.com/experts/people/felipe-moura, Founder of

BrazilJS braziljs.org and Nasc nasc.io.

Website: felipenmoura.com / nasc.io Twitter: @felipenmoura twitter.com/felipenmoura

Facebook: fb.com/felipenmoura LinkedIn: goo.gl/qGmq

Changing the world is the least I expect from myself!

# Naveen Chawla (2 months ago)

I'm not in TC39, sorry if I sounded like I was, just voicing my opinion.

I think the example you gave is better served by throwing the exception from inside "query", instead of doing a "typeof" with the proposed operator afterward.

I find "type branching" normally to be a cumbersome logical flow. Ordinarily the branching can be factored into a common "method" between the types, so that logical flow doesn't need the branching at all (the method "override" does it for you), but in the case of a "mouse" operator, you could often be dealing with completely different types, for which a "common method" may not make sense in the logical flow, thereby necessitating the "type branching" featured in all your examples so far.

To me "type branching" is a non-communicative style of programming. The reader may not know the exact "type" of a particular property or method/function's return value, even more so in JavaScript. Even worse if the method may return different types depending on conditions. It is this lack of clarity that I think can introduce bugs. The remedy, in my mind, is a consistent (non-branching) logical flow. I think that language constructs should encourage that type of programming and discourage type branching (and other patterns that risk having a lack of logical clarity).

Just my opinion, as I said. Disagreements welcome.

# Andrea Giammarchi (2 months ago)

You keep diverging from the intent, basing your answers on my quick'n'dirty examples. I agree my examples are probably not the best looking, but there are no solutions right now to retrieve one part of th echain that failed, if not repeating the chain, and eventually thesame errors, on the right hand side.

This is what <? would like to solve, and I've created a mice.trap [1] module so that maybe devs can start playing with the idea, and come back with a better experience/understanding of such idea.

The point about PHP is also somehow valid, but I wasn't aware about the fact we have other PL constrains with JS syntax.

[1] WebReflection/mice.trap#readme

# Claude Pache (2 months ago)

Le 6 sept. 2019 à 14:35, Felipe Nascimento de Moura <felipenmoura at gmail.com> a écrit :

Doesn't that bring risks to breaking the web?

You seen, many, MANY servers running php have the "shot-tags" feature enabled, in which pages with <? and ?> will be interpreted. In this case, any html page with embedded scripts using this operator, or event .js files when the server is configured to also run php in them, will break.

Or am I missing something here?

[ ]s

Any future PHP file that incorporate that syntax will almost surely refuse to compile on servers that has short-tags enabled, making the problem evident before it produces something useful on the web. This may be an issue, but this is not what “breaking the web” is intended to mean. Existing, untouched content will not break. Carelessly updated content might break, but that’s not fundamentally different from any other careless update.

(If anything else, it may convince people that having different configuration settings w.r.t. short-tags in development environment and in production environment, is a very bad idea...)

# Naveen Chawla (2 months ago)

Typically, "dot" expressions navigate through values of different types, making "type branching" the inevitable next step in those cases (unless you introduce a common method for further processing for each of those types). So I'm not sure how ultimately that would be avoided.

# Andrea Giammarchi (2 months ago)

Indeed I'm not super convinced myself about the "branching issue" 'cause const result = this?.is?.branching?.already and all I am proposing is to hint the syntax where to stop in case something else fails down the line, as in const result = this.?.is<?.branching?.too to know that if any other part is not reached, there is a certain point to keep going (which is, example, checking that result !== this)

# Jordan Harband (2 months ago)

Syntactically marking, in a chain, what you'd like the final value of the chain to be, seems interesting - forcing optionality into it seems unnecessary, though, if such a syntactic marker could be attached to all forms of property access.

Something like: a.b>.c.d or a?.b>?.c?.d or a>[b][c][d].

(Obviously, the > won't work with bracket, and any syntax for normal

properties that only applies to dot and not also bracket would somewhat be a nonstarter; but the specific syntax can be bikeshedded separately)

# Andrea Giammarchi (2 months ago)

It's <?. though, not >

# Tab Atkins Jr. (2 months ago)

On Fri, Sep 6, 2019 at 8:04 AM Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

Indeed I'm not super convinced myself about the "branching issue" 'cause const result = this?.is?.branching?.already and all I am proposing is to hint the syntax where to stop in case something else fails down the line, as in const result = this.?.is<?.branching?.too to know that if any other part is not reached, there is a certain point to keep going (which is, example, checking that result !== this)

Important distinction there is that ?. only "branches" between the intended type and undefined, not between two arbitrary types. The cognitive load between those two is significantly different.

In particular, you can't do anything with undefined, so foo?.bar.baz has pretty unambiguous semantics - you don't think you might be accessing the .baz property of undefined, because that clearly doesn't exist.

That's not the case with mouse, where it's not clear, at least to me, whether foo<?.bar.baz is doing (foo.bar ? foo.bar : foo).baz or foo.bar.baz ? foo.bar.baz : foo or even foo.bar ? foo.bar.baz : foo. All three seem at least somewhat reasonable, and definitely believable as an interpretation!

# Andrea Giammarchi (2 months ago)

To better answer, let's start dropping any direct access and put a payload in the mix.

As example, in the foo()?.bar.baz case, you might end up having null or undefined, as result, because foo().bar existed, but bar.baz didn't.

In the foo()?.bar?.baz case, you might end up having foo().bar, because bar.baz didn't exist.

But what if you are not interested in the whole chain, but only in a main specific point in such chain? In that case you would have foo()?.bar.baz ?? foo(), but you wouldn't know how to obtain that via foo()?.bar?.baz ?? foo(), because the latest one might result into foo().bar.

Moreover, in both cases you'll end up multiplying the payload at least * 2, while the mouse trap will work like this:

foo()<?.bar?.baz

if either foo().bar or bar.baz don't exist, the returned result is foo(), and it's computed once. You don't care about foo().bar if bar.baz is not there, 'cause you want to retrieve foo() whenever you have a failure down the chain.

Specially with DB operations, this is a very common case (abstraction layers all have somehow different nested objects with various info) and the specific info you want to know is usually attached at the top level bject, while crawling its sub properties either leads to the expected result or you're left clueless about the result, 'cause all info got lost in the chain.

The foo()<?.bar.baz case is a bit different though, 'cause if foo().bar existed, there's no way to expect foo() as result, and if it's bar that you're after you can write instead foo()?.bar<?.baz so that if baz is not there, bar it is.

This short-circuit the need for ?? in most cases, 'cause you already point at the desired result in the chain in case the result would be null or undefined.

However, ?? itself doesn't provide any ability to reach any point in the previous chain that failed, so that once again, you find yourself crawling such chain as fallback, resulting potentially in multiple chains and repeated payloads.

// nested chains
foo()?.bar.baz?.biz ?? foo()?.bar.baz ?? foo()?.bar;

// mouse trap
foo()?.bar<?.baz?.biz;

Above example would prefer foo().bar if it exists, and if either bar.baz or bar.baz.biz returned null or undefined.

I hope this clarifies further the intent, or the simplification, that such operator offers: it's a complementary hint for any optional chain, it doesn't have to be used, but when it does, it's visually semantic in its intent (at least to my eyes).

# Michael Luder-Rosefield (2 months ago)

This is getting very reminiscent of my 'forwarding ternary' operator (or whatever I called it) I suggested a couple of years ago. I believe you were involved in the discussion, Andrea...!

const val = foo() ?!
  (x) => x.bar.baz :
  someFallbackValue;
# Andrea Giammarchi (2 months ago)

Interesting I forgot about that, but it wouldn't cover the "trap here" use case.

foo().bar ?! what => what : what;

I'd like to forward foo() here

# Naveen Chawla (2 months ago)

There has to be a better pattern than returning the "foo()" if the baz property doesn't exist.

I'm curious what you would want to do with the resulting "foo()" anyway. I can imagine a flow where I want "bar", and it doesn't exist it doesn't. I cannot imagine wanting the "foo()" in place of it. There is type unpredictability in the result, so subsequent operations would normally expected to be impossible without type-branching. Hence my question to you about what you would typically want to do with the "foo()" if that was the returned result.

# Andrea Giammarchi (2 months ago)

require("module")<?.default is the easiest use case for this, as initially explained.

db.get(SQL)<?.rows?.[0] the most common use case, for queries you know that won't fail but might not return the desired result, so that you end up holding the top most object with all the informations, instead of simply ending up with undefined. This works well with destructuring too.

const {rowsCount, id, name, email} = db.get(SQL)<?.rows?.[0];
if (rowCounts === 0)
  askUserToRegister();
else
  showUserDetails();

As mentioned, there is a module that let you explicitly use this operator through a callback that tries to be as safe as it can (void after first .trap access + self clean on next microtask), so manye we'll come back to this discussion once we all understand the use case and why it's actually very useful in some circumstance.

# Andrea Giammarchi (2 months ago)

so maybe we'll come back...

# Naveen Chawla (2 months ago)

"resultsContainerOrSingleResult" appears to be the end variable. I just find this "shoehorning" to be a sacrifice in code clarity and manageability. "rowCount" would be undefined if greater than 0 in the 2nd example, it seems. Surely that is a confusing behaviour, if not bug prone

# Andrea Giammarchi (2 months ago)

I guess we have a different opinion about what's confusing and what's not. To me having a ?? with potential side-effects is more bug prone than the proposed mouse trap, as it's subtle, yet "promoted" by the ?. + ?? pattern.

# Naveen Chawla (2 months ago)

I wasn't comparing it to your ?? variant of the same cases, which has the same issues - but only because that is a curious way of using ?? in the first place. Ordinarily ?? would be used to resolve to a value of the same type to allow clear unambiguous data flow through the variables.

Rather, I'm comparing it to

const resultsContainer = db.get(SQL), firstResultIfExists = rows?.[0] ;

This allows re-use of the "resultsContainer" (if desired) without creating ambiguity about each variable's meaning. Otherwise if shoehorned into a single variable and a developer accidentally makes a wrong presumption about the "type" without performing an explicit "type check" (of some kind) on the resulting variable, the function would just crash. I know JavaScript already allows this via ?? as it's a dynamic language, but the <? operator pretty much encourages it.

The "destructuring" example produces a buggy "rowsCount". It can only ever be 0. However, another developer may be tempted to say `else if(rowsCount >

1)` not realizing that it's undefined if there are actually any rows. This is what I mean by confusing & bug prone results of the pattern.

# Andrea Giammarchi (2 months ago)

Yes, we can all use N variables in the middle of a chain, but any.thing().that.queries().rows is a more common/natural pattern.

Like in everything in JS, developers need to know what they are doing. Every operator can create bugs (i.e. NaN results or bad string concatenations instead of sums) so I'm not sold on developers being confused: they either understand the code they are writing, or they'll have code reviews from their peers that hopeful understand the code.

If nobody understands the code, then the code should be changed (as in: nobody needs to use an operator they don't get).

As summary, I see more advantages than disadvantages, but I've created a module to solve the same issue to let developers play with it and see if this might be an interesting extra.

I am not planning to keep stating I don't find it confusing though, and if everyone else feels like it's confusing, I can live with it and move on without the mouse trap.