I don’t want to come off too harsh, so I’ll preface that I respect the author and mean all of what follows constructively.
With that said, this seems like a recipe for debugging nightmares. Getters and setters already make life difficult, hiding statefulness and complexity in a way that’s not apparent at the call site (you don’t even know it’s a call site!). Adding magic where sometimes a value is computed and sometimes it’s not amplifies those problems.
Maybe my FP is showing, but all of this could be much more easily accomplished with plain value type data structures and functions. Computed values can be derived either at construction time (ideal, fewer nullish properties and less chance of passing incomplete values off to something that expects them to be complete) or by passing the structure to a function (or calling a method if you must, but here `this` is just an implicit argument to allow access to other properties of the structure).
Replacing a computed value is as simple as constructing a new value structure with the value set explicitly.
All of that is a lot easier to reason about (in terms of just reading the source to see what it does), almost certainly has better performance characteristics, and definitely makes concurrency less complex.
These principles work very well in a general FP environment, but they can be applied to great benefit even in more imperative environments like JS.
I misspoke about the getter (was focused on getting dinner). I should stop at saying the purpose of the getter is to return the value. Using it as the setter as well confuses the purpose for the hidden property just smells wrong.
Interestingly enough, in Python, the minimal, official style guide of PEP 8 actively suggests using properties (the equivalent of the JS getter/setter syntax) for simple things: https://www.python.org/dev/peps/pep-0008/#designing-for-inhe... (See the section that starts with "For simple public data attributes, it is best to expose just the attribute name, without complicated accessor/mutator methods.")
I think in practice this ends up being mostly a function of what's idiomatic in the ecosystem. The most common JS style guide recommends _against_ using getters and setters, so it would be surprising for something in JS to do so. The most common Python style guide recommends _for_ using properties, so the syntactical near-equivalent ends up being something "expected".
> Note 2: Try to keep the functional behavior side-effect free, although side-effects such as caching are generally fine.
Personally, and I'm happy to to be wrong, I can't think of any situation where mutating data in a getter is a good idea... in any language ever. Every single time I've caught myself thinking about it, if I take a step back, what I'm about to do is hot garbage and thankfully my hot-garbage-sense caught on.
I think the context is important here - it's in the "Designing for Inheritance" section. If you're doing that, you're already pretty far in the OO thicket which a lot of Python users prefer to avoid. Actively encouraging properties is not generally a widely held view.
No mutation, no getters (function or implicit), dead simple to use, dead simple to reassign (just call the function again with the same data but provide a new `id` and super easy to understand at a glance (easier with types, but I digress).
> prototype-based-like language
One of the benefits of the approach I describe is you’re no longer working in a prototype based language. I mean, of course you are, but you don’t have to care.
Edit: even on mobile I couldn’t stand the formatting.
Edit 2: since I mentioned performance in my original comment, I’d add that there are two benefits here (one real, one possible with JITs): getters and setters (and anything with Object.defineProperty) perform worse than regular values; if you program in this style, the JIT has an opportunity to recognize that your values are generally immutable and optimize object construction e.g. with the spread operator as persistent data structures the same way FP languages do.
seem like you have some Fear Uncertainty & Doubt (FUD) about this language feature.
rather than cut off this feature & not use it, do you have any thoughts for how an ide or debugger might present some up front information about the nature of said objects? how can we reduce the risk of using the language via tooling? it'd be nice in my opinion to not retreat from the language's flexible, powerful, useful expressiveness?
I think you’ve misunderstood my reaction. I’m certain, and I have no doubt. I’m not motivated by fear, I’m motivated by experience. These kinds of dynamic features that mimic static features are harmful to code comprehension and maintenance.
Since you asked, the tooling is pretty goodish with VSCode if you use typescript, but still misleading for dependencies (getters are just treated as readonly properties, setters are indistinguishable from writeable properties).
I’ll admit I’m in the same boat as the OP: overwriting accessors in this way sets off alarm bells internally based on my own past experience of writing code that’s too clever for its own good.
But to turn this on its head, can you give an example of the kind of divine, really nice solution you’d get from using this?
I apologize if my certainty came across as superiority. It wasn’t meant that way. I’m certain because of what I’ve learned, and sharing that to hopefully help others shorten that learning process.
> this conviction that these language features are never the right answer?
I wouldn’t say never. I can think of use cases for it, but they’re all generally around interop with external interfaces that expect this behavior. Even then the example is better served by plain value types, but there are more complex cases where I would use something like this (or a Proxy, or other dynamic dispatch techniques).
> In my experience, teams & individuals can have a lot of bad sad mad anecdotes they make for themselves. But other people can find & use the same tools & techniques very stably, successfully, & to good effect.
Maybe so. But if I had to guess, I would say that if those teams have a reaction to constructive critique like yours, it’s unlikely they know whether their work is effective or not.
> I don't fully believe you, that you have this experience.
Okay? Then don’t hire me I guess. I’m not here to prove anything, I was trying to offer a simpler solution and explain its benefits.
> That you've earned this ability to trash & insult a rather-wide piece of JavaScript & it's use. In a complete & total way, you've said no, that you alone are the only important voice & that you have total knowledge. This isn't only harsh... it's other unfavorable things too.
You’re wildly misinterpreting my comments. I’m not trashing anything. I’m certainly not saying I am alone in expressing anything like this. I stand on the shoulders of giants. The person who first brought the benefit of basic value types instead of dynamic behavior to my attention was a former coworker, who led me to check out Clojure, which improved my work (at least) tenfold. I’ve learned more from paying attention to ML family languages like Elm and Haskell.
> Meddling with descriptors is absolutely something that can go wrong, and it's very hard to know it really for sure is a good case- often you'd be better without. But there are also many good cases where these techniques & knowledge of them is absolutely divine, enables really nice solutions. If you need to instrument some existing 3rd party objects? Fantastic, absolutely fantastic.
It seems like we might not be as far off in our thinking as you assume. But I think there’s a risk in presenting something like this as a general technique rather than a special case. And I think there’s a benefit in presenting a simpler alternative that would be better for 99% of cases.
I'm fine with alternatives but I didn't feel like any space was left to teach anything but your desired magic bullet that killed this feature dead & I still don't see that you accept limitations or alternatives in your hardline stated views.
> A pattern that has come up a few times in my code is the following: an object has a property which defaults to an expression based on its other properties unless it’s explicitly set, in which case it functions like a normal property. Essentially, the expression functions as a default value.
Having a dynamic default value makes sense, but instead of having the object reconfigure itself on the fly, why not design a single virtual field that intelligently chooses between two distinct sources of data? For example, you could write a class like this:
class MyObject {
#customFoo;
get foo () {
return this.#customFoo ?? this.#getDefaultFoo();
}
set foo (val) {
this.#customFoo = val;
}
#getDefaultFoo () {
// dynamically generate and return foo value
}
}
That way you could get a dynamically created value up until a custom value is set, with the option to switch back to the dynamic default simply by setting the field to null or undefined.
Also, I'm not sure if you were implying that it's a good thing, but the concept of "properties which execute code to produce side effects when they are get or set" seems a bit risky to me, especially the "get" part... it can make it very difficult to reason about the state of a system when simply accessing a property on an object has side effects. Obviously side effects don't need to be avoided at all costs, but IMHO it's best if they only occur during explicit function calls.
Changing the prototype of objects after they've been initialized causes deopts for JIT compiled code. Its convenient, but you probably can't use this pattern in performance-sensitive code.
Interesting ways to stretch an objects capabilities but I would likely use a class for stuff like this, in which case the problem would be trivial to solve, in a highly legible way.
I'm probably missing something since the author is a renowned author, and I'm just a dabbler. What am I missing though?
Exactly what I was thinking! I've used this computable-valye-but-overridable-with-explicit-value pattern a couple of times, and so long as you stick to a good naming convention it works great.
It seems to me that you could achieve all of this much more simply by just coalescing (`??`) the mutable value to the default calculation inside the getter.
If the author is reading this, the random angles in the background colors are fairly distracting and give no benefit to the article or the site. Just because you can do something doesn't mean you should.
my first programming job out of school involved working on a simulator with replay scenarios. a subtle bug arose due to what I assume was an optimization in which whenever a getter was called the value was incremented. during replay it would skip steps.
it was not well documented. ever since I've avoided mutating getters.
With that said, this seems like a recipe for debugging nightmares. Getters and setters already make life difficult, hiding statefulness and complexity in a way that’s not apparent at the call site (you don’t even know it’s a call site!). Adding magic where sometimes a value is computed and sometimes it’s not amplifies those problems.
Maybe my FP is showing, but all of this could be much more easily accomplished with plain value type data structures and functions. Computed values can be derived either at construction time (ideal, fewer nullish properties and less chance of passing incomplete values off to something that expects them to be complete) or by passing the structure to a function (or calling a method if you must, but here `this` is just an implicit argument to allow access to other properties of the structure).
Replacing a computed value is as simple as constructing a new value structure with the value set explicitly.
All of that is a lot easier to reason about (in terms of just reading the source to see what it does), almost certainly has better performance characteristics, and definitely makes concurrency less complex.
These principles work very well in a general FP environment, but they can be applied to great benefit even in more imperative environments like JS.