New Variable Declaration keyword. const, let, var ..... get ??

Is it time we introduce getters for variables??????

Let me explain:

Some Context:

We always order booleans by their computation complexity ( cheapest to evaluate first ).
This is to avoid evaluating expensive parts of a boolean when the results of that computation is already known.

Example:
if (module.cheapToCompute() && module.expensiveToCompute()) { 
  // ....
}

Making it readable is not always possible...

Ok, I'm lying, it is always possible but it's not always convenient.

Example: Let's check if a user can see a special resource

if (resource.everyoneCanSee || user.auth?.checkAdminRights() || user.auth?.getSpecialRights().includes('seeRessources')) {
  // ....
}

As you can see It's not quite scannable... Let's refactor and make it more readable. I'm going to sparkle intermediate variables here and there.

const hasAdminRight = user.auth?.checkAdminRights();
const hasSpecialRights = user.auth?.getSpecialRights().includes('seeRessources');
const canSeeResource = resource.everyoneCanSee || hasAdminRight || hasSpecialRights
if (canSeeResource) {
  // ...
}

That's now super easily scannable and maintainable. :nerd_face:
But the problem is that we are evaluating all those variables and it's expensive. :grimacing:

Proposal:

Why not using a getter instead of directly reading the variable?

How does it work? The rule is simple, the get keyword should work exactly like const. Expect it is evaluated only the second time we try to access it. In other words, it should defer reading the variable to the next time it tries to reference it.

Here is an example usage of the get keyword:

get hasAdminRight = user.auth?.checkAdminRights(); <-- get variable created here
get hasSpecialRights = user.auth?.getSpecialRights().includes('seeRessources'); <-- get variable created here
const canSeeResource = resource.everyoneCanSee || hasAdminRight || hasSpecialRights // <-- they get evaluated here
if (canSeeResource) {
  // ...
}

The code is now self documenting AND we evaluate the minimum.
:tada::tada::tada:


Thanks! :v:

Let know if that's dumb.

We sort of have that already. It works like this:

const hasAdminRight = () => user.auth?.checkAdminRights();
const hasSpecialRights = () => user.auth?.getSpecialRights().includes('seeRessources');
const canSeeResource = resource.everyoneCanSee || hasAdminRight() || hasSpecialRights()
if (canSeeResource) {
  // ...
}

You use () => instead of get at the declaration site, and whenever you want to use a "getter" variable, you add () afterwards.

1 Like

It's true, we can recreate that behaviour using functions.
We could also use that workaround instead of using object/instance getters.
Can't we argue the same thing with the class keyword? We could implement the same things using a function, but the class syntax has its advantages.

The goal here is to not implement the workaround ourselves. The whole value lies in ergonomics.
I think your comment reinforces the value because getters are really just that, syntactic sugar around a function call.

let me know what you think :)

And I wish we used the work-around more :) - I'm not a huge fan of the getter syntax. When you see x.y.z, you expect it to be simple property access, but every once in a while, getters can be used to break your expectations, letting arbitrary logic happen right in the middle of that expression, and making it harder to follow the logic that's happening.

That being said, the purpose of object getter syntax isn't to just reduce verbosity. The theory behind them, is that they're supposed to allow you to add behaviors to an existing class/object, without breaking backwards compatibility. In other words, the getter syntax doesn't exist simply because users didn't want to bother tacking on () after accessing a property, they had a specific purpose beyond that.

The same can't be said for "variable getter syntax". As far as I can tell, the only purpose would be to make optimized code slightly less verbose (you don't have to tack on a ()). And, TBH, I prefer having the () tacked on afterwards, as that explicitly tells me that there's extra behavior happening at this location besides a simple utilization of a variable. The use of the small () characters makes it much easier for a newer person to read and follow the code, as there's less implicit magic happening.

2 Likes

Related: Destiny Operator

1 Like

Yup, getters exist to enable easier refactoring. (And, to an extent, to allow chained access/mutation without annoying/complicated get()/set() chains.) A class is often not limited to local usage, so rather than requiring authors to change every single usage across their codebase when a property needs to be changed to a method, getters allow the change to be made invisibly in the class itself, at the cost of some opacity for users of the class (who now might be invoking arbitrarily complicated/expensive code when they do a property access, which is normally more or less free).

None of these reasons apply to local const variables. They're only visible to a small amount of completely local code, so refactoring from a static value to a function is cheap, and their const-ness means you don't get the benefits of a compact mutation syntax.

(One exception here is modules - you can currently export a static variable from a module, and there's no way to silently upgrade that to a get/set method pair, but they share the "non-local" aspect of class properties. But adding some sort of export get/export set syntax is a different request from this.)

1 Like