The article (and author’s referenced tweets) center on private field access failures with Proxy, but (and) the same problems arise if you override subclass members which defer to their super for private access. This is something that works perfectly fine with the `private` keyword semantics in TypeScript which predate runtime private class members. I agree that the actual runtime behavior is harmful, and I will only use it very carefully with very skeptical consideration of whether there’s any value in using it.
Fortunately symbols provide exactly the same runtime encapsulation semantics with none of the footguns. It’s a little bit more ceremony, but you can achieve everything a private class field would accomplish by using singleton symbols as long as you don’t export them or otherwise expose them to a consuming scope.
Yyyyyeah, classes in js are pretty bad full stop. For instance, passing a method as an argument will do awful things unless you bind it to a this first. Gross.
Still, it's interesting to me that Proxy is more valuable to author than private member variables. Curious about some common use cases.
Totally agree. This is very much a symptom of JS’s class-ish syntactic sugar. Access modifiers have to be a runtime check, and be bolted onto an environment that was built around function scope being the only way control access.
Using typescript to handle things like access modifiers at compile time makes everything much more predictable.
TypeScript now supports both the ECMAScript #private - it has no choice in the matter, since it's in the language now - and their own development-time "private". It sounds messy but that's the fault of those who wanted it in the language, as runtime checks.
I agree with the other comment, closures are far more powerful and flexible. I understand that this OOP stuff in JS was probably a nod to the many programmers in many companies already used to it, to make JS more palatable for them. It's easy to demand millions of people worldwide to adapt, but in business reality it does not work that way. So I grudgingly accept those new(er) things in JS as a business decision and necessity, for the huge crowd of people doing programming mostly just for the money, who had to write more and more code for the web that used to be run as installable software on Windows.
Even without classes there are a ton of footguns involving ‘this’ and method scope. Fat arrow notation papered over a bunch of issues. But I have lost more than a few hours confused as to why changing between map and a loop completely broke/fix behavior. Or seen codebases absolutely peppered with unneeded ‘bind(this)’ like it would ward off evil. I had to spend some time studying before I really grokked it.
It continues to sadden me that the implicit this injection happens only on direct invocation -
let res = obj.method(arg);
and
let m = obj.method;
let res = obj.method(arg);
being different things seems like an unfortunate trade-off (though not knowing the full reasons it was chosen to be that way makes me wary of assuming it was the wrong decision overall).
I've been known, for things where the object is e.g. a bag of components, to loop at construction time and replace all of my methods with pre-bound copies of themselves, but having to make a conscious decision as to whether I need to introduce that extra code (and work for the runtime per-instantiation) still makes me grumble to myself every time.
>Yyyyyeah, classes in js are pretty bad full stop. For instance, passing a method as an argument will do awful things unless you bind it to a this first. Gross.
One of the things that heavy use of JS frameworks will do is make you forget you can pass an object interface to represent continuations. The DOM spec defines the EventListener type, in TypeScript notation, as:
What does this type signature instruct us to think?
That a function closure should reference objects in its scope, no need for `this` binding, or an object interface, appropriate to the domain of the deferred caller should be referred to and called (e.g. `handleEvent` in the case of DOM's EventTarget interface, but one could also imagine `{ onTimeout() : void }` if the spec authors were consistent, perhaps accepting a key to differentiate calls in the same vein as event types), also eliminating the need to bind `this`. The addition of `WeakMap`\`WeakSet` to the runtime eliminates the need for manual resource management.
But React, the most popular JavaScript library (and its not alone in this), makes this pattern impossible: you can only pass functions to event listeners. So everyone then growls about `this` binding.
Isn’t the point that private fields remain private implementation details of the class in just about every language that has them?
As a library author, if I declare something as private, it means you the consumer, may damage internal state in some way by directly mutating those fields.
If they do need to be read or mutated, that’s a good case for adding to the public API, not complaining that reflection tricks can’t get at something you shouldn’t have access to. Not to mention how incredibly fragile your consumer is going to be when upgrading… I feel like the JS folks are learning the same lessons the hard way that the Java folks did 25 years ago.
Isn’t most Javascript rendered useless by the time it is ready to be upgraded because all of its dependencies have moved on to not so greener pastures?
On a serious note. Keeping up with changes in the Javascript community seems like a full time job. I don’t know how they get any work done.
The major frameworks of today were already well established then, and the last really big update to the language in 2015 had already been in fairly wide spread use via transpilers (traceur and later 6to5, which became babel).
Honestly, in the last 13 years, many mainstream languages have gone through similar churn. JavaScript has gone through more than most, to be sure, but nobody complains about Rust or C# nearly so much.
And yet, I find myself struggling way more with Objective-C/Swift and Java/Kotlin deps than I do with JS ones. Dependency upgrades can be problematic, sure, but JS is no worse than any other language. I never had to change compiler flags or mutate internals of a library in npm the way I need to do with cocoapods.
> Honestly, in the last 13 years, many mainstream languages have gone through similar churn. JavaScript has gone through more than most, to be sure, but nobody complains about Rust or C# nearly so much.
Rust appeared 7 years ago. Rust did not went through any churn. Arguably, the first iteration hasn't even ended yet.
They really aren't. Editions are just a mechanism to allow Rust developers to push backwards-incompatible changes as opt-in features. I repeat, Rust hasn't even went through its first iteration, and it shows a fundamental lack of understanding and insight to argue otherwise.
I don't think it was a joke, and the problem is real. Last year I had to spend significant amount of time updating a basic frontend that was "only" three years old. "npm install" told me I had over a hundred security problems (most weren't really applicable or a big deal, but still), but updating things was hard as almost everything had incompatible changes; some had several major version bumps. Since I didn't write the original application and wasn't familiar with all the libraries used it felt like joining Game of Thrones in the middle of season 4 and figuring out what was going on. All in all, I spent about a week of full-time work.
The backend was written in Go; everything could be updated to the latest version without any incompatibilities at all.
People having been complaining about incompatibilities in Rust too, but Rust is a much newer language, and I think things have settled by now as well. I don't know much about C#.
Sure, and in another three years you'll be saying the same thing about go because the introduction of genetics has changed everything. Java went through the same thing with the transition of java 9, introduction of lambdas, etc. Python went through 2 to 3 in the same time period. C++23 is in the works, and many parts of the ecosystem are just now getting support for 11.
I'm not saying the problem doesn't exist, or that's certain parts of the ecosystem haven't experienced more churn than others. I am saying that "js churn" has been a meme joke for a long time, and has been old for almost as long.
> in another three years you'll be saying the same thing about go because the introduction of genetics has changed everything
I'm not so sure about that; things haven't really changed that much. A language gaining new features doesn't typically break everything, even when libraries start adopting them.
But even if it does: sure, languages can have their "everything changed" watershed moments, but no such watershed happened in JS in the last few years, yet updating things is still a lot of effort, and a bit of a pain.
It actually has nothing to do with the language; it's just the mindset. For whatever reason compatibility is just not high on the list of priorities in the JS ecosystem. I maintain Go packages that have been compatible for over 10 years; I make special effort to ensure they remain compatible, and even if a compatibility break is required I try to minimize it. There is nothing in JS or npm preventing the same kind of mindset.
Either way, it's not a "meme joke" and it's certainly not "old" as the problems still exist today. If you (or someone else) would say "I think the advantages of moving faster are more important than remaining compatible": okay, fair enough; I can see that's a reasonable point of view, even though I personally don't agree with it. What I always find off-putting is the casual dismissal of the problems. The Python 3 transition could have been handled better and dependency management remains a bit of a mess in Python: I don't see many Python developers deny these are (or were) real problems.
Eh, most of the time, if you use private, what you really wanted was protected.
Private causes extensibility issues (no access to private fields in child classes). Not sure if the article addressed this or not because it's currently hugged to death.
She wants her users to be able to use Proxies over her classes. Private members make a class ineligible for proxying. So she'd rather use private-by-convention.
There is actually a stage 2 proposal "Function Implementation Hiding" that is being pushed through despite the many very legitimate and thoroughly described concerns that people have been voicing on GitHub and everywhere else this topic is brought up.
A quick glance over the GitHub issue titles should give you a general idea.
Whilst I don't personally share all of the concerns posted there and even believe some of them are a little bit silly, I have to say that I do not at all like the way most of the discussions are abruptly cut short or simply left unanswered, and how difficult questions or ideas that aren't entirely in line with the original proposal are immediately declared nonsensical/invalid or shot down without any further exploration at all.
The above is a common theme in that repo and not difficult to find. I've never seen that kind of behaviour in any other proposal at that stage. Very sad to see.
Personally I feel like private JS class fields are against the spirit of a scripting language and are harmful to debugging and inspectability (in the way they are implemented).
I've stuck with TypeScript private fields which does exactly what I need.
In general, the people writing language features out of fear cause the bulk of the problems.
Js had coped quite well without having private fields. We had conventions of using this._field that served fine, that cautioned users sometbing was internal-ish without binding them, without preventing experimentation.
The private function implementation is more bad language design seeming on an inevitable course to make the language worse & less capable. It's more fear, more adding nothing, more creating friction, like private fields.
That all said, it doesn't seem like proxies ought be broken by private class fields. That's another heap of scorn placed upon this already tragic & harmful misfeature.
I also argued strongly against this proposal. To me the biggest problem with private members, as it was implemented, is that is takes classes from “syntactic sugar on top of prototype inheritance” to something very different than that. It makes the mental mode of the language a lot more complicated, and without a lot of benefit.
I never understood why TC39 was so intent on this “hard privacy” stance they took, as without it there were much better and more sensible compromises that could have been made. (Especially since JS is an interpreted language - if someone wants to work out what private variables are in your code, they can always just inspect the source.)
Even worse, it came at the expense of other things.
Records and tuples for example aren’t controversial. They are primitives and therefore solve a few quirks that come from objects. They should also radically improve performance because users can’t change types or add/remove properties causing less optimized lookups.
Instead, all that time went into a controversial feature that has made things worse without solving any real issues either.
I've read the documentation about Proxy, and I am still not sure what it does, and why the hell I would ever want to use it. Libraries relying on such a feature are best avoided, in my opinion. Actually, most JavaScript libraries are best avoided :-)
> JavaScript is a high-level, often just-in-time compiled language that conforms to the ECMAScript standard. It has dynamic typing, prototype-based object-orientation...
whahaha. i took the exact opposite approach and swore off proxies years ago for exactly this kind of edge case. there's scenarios where they just don't work and they'll bite you and i don't want to invite more gotchas into my code.
The author is not interested in writing proxy code, their interest is to allow other to use their classes together with libraries that happen to use proxies.
I guess that's noble to try and make your library compatible. I don't think they're giving up much by dropping native private vars. I'm in the camp of "if this var is documented as private/do not use and you use it anyway and then I change it and your code breaks, then that's on you". i.e. just prefix vars you don't want people to use with `_` or something.
Sure, but extensibility often comes with complexity. If I'm making something non-extensible it's to keep things simple or to prevent a bunch of bugs around people trying to extend something with complicated state.
They always have the nuclear option of forking and making those fields no longer internal. They'll have to take on the maintenance overhead that I shirked, but that should be fine if they need it that badly.
> They always have the nuclear option of forking and making those fields no longer internal.
while that is always possible, it is not always reasonable.
If you take firefox as the example, the removal of the XUL extension api is one such. And while there's many a forks to keep the old XUL api, none of them really caught on, and so support for them such as maintaining security patches etc fails.
That feels like an argument for 'final' more than 'private.'
Personally, at least for library code, I tend to assume -somebody- will extend any given class of mine at -some- point and would prefer they had a reasonable time doing so, but I use 'prefer' advisedly since there are absolutely situations where doing so would almost certainly be a footgun and in that case I'd rather dissuade the user before sinking time into the idea.
If I am understanding the issue here I don't believe C# would suffer from this issue since reflection works not only on public method access where those methods use privates internally but in fact could access privates directly.
In C# private is more of suggestion than an enforced concept since you can do anything you want with reflection, which is very useful and necessary sometimes. Keeps normal code clean and encapsulated while allowing one to go under the covers as needed.
Seems very strange that not only can you not get to private members directly in JS but even worse you can't call a public method that use a private though a proxy, bizarre and against what I would think JS to be.
Unless you're going 100% immutable everything (which the language isn't great for), advocating for making all members public in java seems roughly as sane as advocating for declaring all variables and returns as Object.
Imagine you have a type that represents some domain state. It has a bunch of fields that are used to represent this state. But that state has invariants. There are certain combinations of values that the fields are not allowed to take and they relate to each other in complex ways. A well designed interface to this type is such that every sequence of methods called on one of these objects will maintain the invariants. If you instead just directly expose the fields then absolutely nothing guarantees that the invariants hold.
Yes, if the first thing you do when you create a private field is write a public getter and a public setter then you haven't gained anything. But the power is in not writing these methods.
Proxies exist pretty much for one purpose and one purpose only: to implement membranes, like described in <https://github.com/ajvincent/es-membrane/>, where you build on demand a whole parallel universe of proxies that stand in for objects existing somewhere else, and have wrapper functions convert between those proxies and real objects. Any other use is incidental and unsupported.
Admittedly, not many people know this, so those who are not deeply familiar with TC39 proceedings commonly think of proxies as just a way to hook into arbitrary behaviours of a single object, like property accesses, then are treated to a bitter surprise when proxies don’t work like what they thought or wanted.
Still, the opening example is simply a misuse of Proxy, so it’s rather unconvincing.
If they didn’t want other use cases, they should have rethought the feature.
The reality is that proxies came first. They see a LOT of use in reactive libraries making it very likely that your classes get passed into one these proxies (and breaking if private fields are used).
They knew all this when ramrodding it through over the many protestors warning about this and other things. The fault is 100% on the hubris of the spec committee.
> Only two days ago I chose to rewrite a chunk of code simply because I couldn’t call a private function that did precisely what I needed.
It may have done what you needed in the current version of your library, but just look at it from the perspective of the library author. If they ever want to refactor, or maybe remove, that private function, it would break a lot of people's code. And even if it were marked private or deprecated, it would still break a lot of people's code. You could say "but if your code gets broken it's your own fault" and yes you're right, but it wouldn't stop those people from loudly complaining to the author.
> you're explicitly taking on the maintenance burden
How are you not taking the burden of maintenance when writing code that uses private-by-convention properties? The difference seems pretty abstract, a fork will require the same rework when internal properties change.
So basically it's the same result (you need to manage updates yourself and test your changes) except that your modifications are now scattered around dozen of repositories instead of one, and what's the benefit of this added complexity compared to a patch? None that I can see?
I think it’s commonly understood in the JS community: If you modify a library property with an underscore, you are on your own.
Adding a broken feature that was widely criticized for years (more than any other JS feature before it was added — including type coercion) just so some maintainers don’t have to tell users no about one particular thing isn’t a great idea.
It’s even less of a great idea when you consider that modules exist. Add a variable without marking it for export and you’re good to go.
Because all the import * as Foo from "foo" produce the same singleton with shared state. What I would like is something like
function(param1, param2){
return module Name {
import {param1} from super;
export let A = param1 + 1;
// This line would cause an error as
// param2 is not imported from the enclosing scope
// export let AA = param2 + 2;
}
}
Singleton is orthogonal to namespaces. One is about data creation and the other about naming stuff.
If I want to create new pieces of data, I have to make an allocation with a constructor. Reusing methods provided by modules is effectively the same as classes (the two being mathematically equivalent, but closures not having all the footguns).
This is true no matter if your code is OOP, imperative, or functional.
> I couldn’t call a private function that did precisely what I needed.
Well, in a neighboring comment someone is suggesting making the private stuff local, so it cannot be called as well. So your problem would persist just as well.
It is a great concept, the only way to force developers that feel entitled to reach out to implementation details and then go cry on social media when their beloved app breaks, to actually follow what is available to them.
Over time people will become dependant on your entire api, bugs and all.
Without private methods or some equivalent you won't be able to change your library without breaking lots of people's code.
It also makes understanding the interface hard, if everything is public, how do I know which methods are intended for me to consume, and which are internal helpers.
Of course, not all interfaces will suit your need and some are poorly designed.
Modules already offer private variants and methods. The only real difference is less boilerplate, more customization, no confusing edge cases, and not allowing the inheritance footgun.
Proxies added new features that couldn’t be emulated. Class syntax added nothing of intrinsic value, but also succeeded in creating an entire generation of “Senior” JS developers who don’t understand prototypal inheritance in general and the weird JS implementation in particular.
This just reinforces my bias that OOP is what is really harmful. Really, what good reason is there to have data and behavior bound to the same object? I’ve never seen a good argument for that. It’s just taken as a given. It seems nice and convenient, but that convenience seems to always lead to unexpected complexity like this.
A while ago I was looking for a color manipulation library, and I saw her Color.js, and loved all the features. Then I saw the bullet point, “Readable, object-oriented API”. Nope. I’m not touching that. I chose culori instead. It’s just a collection of functions that transform standardized color data objects. It’s like lodash for colors. It’s been great to work with.
I agree. Private class fields seem totally unnecessary when you can simply "hide" functions and variables in a local scope and not export/expose it. (Or use symbols as another comment suggested.) I also don't want a library for color manipulation to be using Proxy and other magic tricks, including "this". I prefer plain functions that take any values they need as explicit arguments, and simply return results - without maintaining state, having side effects, pretending to be a plain object, etc.
> I agree. Private class fields seem totally unnecessary when you can simply "hide" functions and variables in a local scope and not export/expose it.
This is not true. Private members are a part of a class interface, while local/internal function/variables are not. They both serve totally different purposes. If you're using private members as a substitution to local/internal members, you're doing it in ways that are fundamentally wrong.
I’m confused by this comment. How are private members part of the class interface? They are internal, not exposed to the outside, so how can they be part of the interface, which by definition is what the class exposes to the outside?
At least in lower level languages like C or C++, private members are part of the interface since they affect how much memory needs to be allocated for an object.
> Really, what good reason is there to have data and behavior bound to the same object?
Behavior is the interface/messages supported by the object, and data is an implementation detail that should not leak.
What's wrong with that?
And why are you pinning the blame on a whole paradigm from a problem caused by how a feature was implemented in a language renowned for being fundamentally broken ?
My question is, why is that necessary? Exactly what is the benefit, except for a tiny bit of convenience? Why is that better than functions that work on well-defined types? It adds coupling, and coupling adds complexity, so if I'm adding complexity, I want to know why it's better before I add it.
> And why are you pinning the blame on a whole paradigm from a problem caused by how a feature was implemented in a language renowned for being fundamentally broken
I think calling the JavaScript/TypeScript of today fundamentally broken is clearly untrue. But anyway, I'm not condemning OOP entirely because of this one feature in one language. I thought my comment made clear that I see this as just another in a long line of complexity I've seen that's caused by this unnecessary coupling of data and behavior.
When I think of the OOP paradigm, I really think of Java, which I would call more fundamentally broken than JavaScript. It's improved over the years by pulling in ideas like lambdas that loosen the straightjacket imposed by its broken interpretation of OOP. Half of the revered "design patterns" that we're supposed to learn for OOP are just to work around this coupling when it's getting in the way. Patterns, even if they're repeatable, are complexity. Have you implemented the Visitor Pattern [0] lately? I did it once and I will never do it again. That kind of stuff ties my brain in knots. If you like pointless puzzles, maybe it's fun, but for me it's just more and more complexity. When I just work with data and functions, most things become much simpler.
> My question is, why is that necessary? Exactly what is the benefit, except for a tiny bit of convenience? Why is that better than functions that work on well-defined types?
The benefit is subtype polymorphism via dynamic dispatch. When values flow through the program they carry the implementations of their operations with them, so a caller can operate on a value without knowing how that operation is implemented; a newly-introduced value can bring a new implementation of an existing operation, and existing callers can use that implementation without knowing that it’s new. There are other ways of achieving this, but OOP is a good one.
I think of subtype polymorphism and dynamic dispatch as two different things, but maybe I'm misunderstanding something here.
Subtyping/inheritance is sometimes convenient, but inheritance tends to be a constrictive way to reuse code that increases cognitive load. If B is a subtype of A, now, not only are the data of B coupled to the behavior of A, but the data or behavior of B _may or may not_ be coupled to A depending on whether B overrides A. More coupling. More cognitive load. And if you change A, you have to be careful that you don't break B. Then if you have C that's a subtype of B, then the cognitive load and possibility of unexpected breakage gets even higher. Ugh. No. I'm a simple-minded person. I don't want to think about stuff like that.
I see dynamic dispatch simply as a form of delegation, which can easily be accomplished functionally.
So, ultimately, again, I see these benefits as being _convenient_ but not _necessary_. And since all design decisions are trade-offs, I look at the downsides of the coupling and find that the trade-offs are simply not worth it.
I don’t think you’re misunderstanding anything, but to clarify, I’m talking about a more general notion of subtyping (e.g. structural, “duck typing” etc), not necessarily inheritance. Instances of A and B can (in Smalltalk parlance) respond to the same set of messages, and therefore be substitutable for each other, without having any inheritance relationship. In that case there’s no coupling of implementation, only the flexibility of being able to swap out an A instance for a B instance without its collaborators being any the wiser.
Really, what good reason is there to have data and behavior bound to the same object?
Caches which require specific invalidation logic.
A good mental model is the query language for a database. The language provides a frontdoor for all the state, so you can't just poke around and mutate things.
Eh. I guess I don't see how `cache.invalidate()` is a drastic improvement over `invalidate(cache)`. And my experience with ORMs has also not been pleasant, although it's been a few years since I've used one. They seem very convenient for 80-90% of use cases, and a nightmare for the rest. And you don't know at what point your query will pass that invisible line into the nightmare zone. You start with something simple and it's very convenient, then as things change and get more complicated, the generated queries become more and more unperformant and confusing.
Only if you understand the details of when the cache needs to be invalidated.
I'm not sure where ORMs entered the picture, but my point is that the database uses the query engine to guard internal caches which are private via the process boundary.
OOP is a technique which shouldn't prescribed for everything. I will say Javascript shouldn't have have classes, but I hope to never need to use JS much beyond glue.
Things that resemble state machines tend to produce reasonable interfaces when exposed in OOP fashion.
The problem is more one of, you don't want everything to be a state machine, and adding the boilerplate tends to overcouple the data. The maximally composable form will tend to extract a little FSM with one variable, while all other relevant data is simply passed into every method.
Yes. I am not against data hiding. I love data hiding. My comment was not related to data hiding. It's simply pointing out that this kind of unexpected complexity seems to always come up in OOP in my experience.
> JavaScript is a high-level, often just-in-time compiled language that conforms to the ECMAScript standard. It has dynamic typing, prototype-based object-orientation...
That’s an absurd reductionist argument. I could argue that the actual implementation of them is subroutines, so it’s actually imperative programming, but that would also be absurd.
There’s also a case that using those methods is indistinguishable from something like typeclasses which don’t involve classes at all.
If we want to be even more pedantic, JS is prototypal and fundamentally lacks classes with objects simply having an optional pointer to B other objects, so the implementation of these methods doesn’t necessarily imply classes.
You also have an argument that proves too much. If this argument is true, then your previous multiparadigm claim is proven false.
In any case, it doesn’t seem like a good faith argument. There’s no reason I ever have to make my own classes in order to make projects in JavaScript. This is all a typical programmer is going to care about and is the spirit of my statement.
Absurb is pretending JavaScript isn't an OOP language.
OOP isn't classes, I suggest education on OOP type theory, and regarding type classes, a few Symon Peyton Jones papers on how type classes in FP relate to OOP.
I still don't get what is wrong with the _ convention. Also please stop complicating the mental model of languages for no good reason (also looking at you C++).
Idk how you can conclude it's a problem with the concept of private fields when the giant shiner is Proxy not reflecting on them like you'd expect.
It's borederline clickbait, or selfbait, as the author believes they've contributed to the ever shuffling stinky pile of hot takes that exist in the web world.
It's really not as exciting to say Proxy, a niche class, than saying there's a problem with a concept of something every day, like private fields.
I doubt the vast majority of you have any experiene with Proxy, but OOP? Im sure you've brooded on a few hottakes about that in your careers.
Is this not more an issue with the proxy implementation? If it deferred to the proxied object's methods, would there be no issue with the proxy attempting to access its private fields?
Apologies in advance if I'm misunderstanding the purpose of proxies.
134 comments
[ 3.1 ms ] story [ 183 ms ] threadFortunately symbols provide exactly the same runtime encapsulation semantics with none of the footguns. It’s a little bit more ceremony, but you can achieve everything a private class field would accomplish by using singleton symbols as long as you don’t export them or otherwise expose them to a consuming scope.
Still, it's interesting to me that Proxy is more valuable to author than private member variables. Curious about some common use cases.
Using typescript to handle things like access modifiers at compile time makes everything much more predictable.
You may also want to read what I wrote a few days ago already: https://news.ycombinator.com/item?id=35653653
TypeScript now supports both the ECMAScript #private - it has no choice in the matter, since it's in the language now - and their own development-time "private". It sounds messy but that's the fault of those who wanted it in the language, as runtime checks.
I agree with the other comment, closures are far more powerful and flexible. I understand that this OOP stuff in JS was probably a nod to the many programmers in many companies already used to it, to make JS more palatable for them. It's easy to demand millions of people worldwide to adapt, but in business reality it does not work that way. So I grudgingly accept those new(er) things in JS as a business decision and necessity, for the huge crowd of people doing programming mostly just for the money, who had to write more and more code for the web that used to be run as installable software on Windows.
That was true long before classes arrived.
I've been known, for things where the object is e.g. a bag of components, to loop at construction time and replace all of my methods with pre-bound copies of themselves, but having to make a conscious decision as to whether I need to introduce that extra code (and work for the runtime per-instantiation) still makes me grumble to myself every time.
Do you mean that 'res' evaluates to a different result in each?
Or that when you call 'object.method()' vs calling 'm()' you would get a different 'this' context?
I meant the 'this' thing - a sibling comment to yours contains the correct code.
Thank you.
One of the things that heavy use of JS frameworks will do is make you forget you can pass an object interface to represent continuations. The DOM spec defines the EventListener type, in TypeScript notation, as:
`(event: Event) => void | { handleEvent(event: Event): void }`
What does this type signature instruct us to think?
That a function closure should reference objects in its scope, no need for `this` binding, or an object interface, appropriate to the domain of the deferred caller should be referred to and called (e.g. `handleEvent` in the case of DOM's EventTarget interface, but one could also imagine `{ onTimeout() : void }` if the spec authors were consistent, perhaps accepting a key to differentiate calls in the same vein as event types), also eliminating the need to bind `this`. The addition of `WeakMap`\`WeakSet` to the runtime eliminates the need for manual resource management.
But React, the most popular JavaScript library (and its not alone in this), makes this pattern impossible: you can only pass functions to event listeners. So everyone then growls about `this` binding.
As a library author, if I declare something as private, it means you the consumer, may damage internal state in some way by directly mutating those fields.
If they do need to be read or mutated, that’s a good case for adding to the public API, not complaining that reflection tricks can’t get at something you shouldn’t have access to. Not to mention how incredibly fragile your consumer is going to be when upgrading… I feel like the JS folks are learning the same lessons the hard way that the Java folks did 25 years ago.
On a serious note. Keeping up with changes in the Javascript community seems like a full time job. I don’t know how they get any work done.
The major frameworks of today were already well established then, and the last really big update to the language in 2015 had already been in fairly wide spread use via transpilers (traceur and later 6to5, which became babel).
Honestly, in the last 13 years, many mainstream languages have gone through similar churn. JavaScript has gone through more than most, to be sure, but nobody complains about Rust or C# nearly so much.
Rust appeared 7 years ago. Rust did not went through any churn. Arguably, the first iteration hasn't even ended yet.
They really aren't. Editions are just a mechanism to allow Rust developers to push backwards-incompatible changes as opt-in features. I repeat, Rust hasn't even went through its first iteration, and it shows a fundamental lack of understanding and insight to argue otherwise.
The backend was written in Go; everything could be updated to the latest version without any incompatibilities at all.
People having been complaining about incompatibilities in Rust too, but Rust is a much newer language, and I think things have settled by now as well. I don't know much about C#.
I'm not saying the problem doesn't exist, or that's certain parts of the ecosystem haven't experienced more churn than others. I am saying that "js churn" has been a meme joke for a long time, and has been old for almost as long.
I'm not so sure about that; things haven't really changed that much. A language gaining new features doesn't typically break everything, even when libraries start adopting them.
But even if it does: sure, languages can have their "everything changed" watershed moments, but no such watershed happened in JS in the last few years, yet updating things is still a lot of effort, and a bit of a pain.
It actually has nothing to do with the language; it's just the mindset. For whatever reason compatibility is just not high on the list of priorities in the JS ecosystem. I maintain Go packages that have been compatible for over 10 years; I make special effort to ensure they remain compatible, and even if a compatibility break is required I try to minimize it. There is nothing in JS or npm preventing the same kind of mindset.
Either way, it's not a "meme joke" and it's certainly not "old" as the problems still exist today. If you (or someone else) would say "I think the advantages of moving faster are more important than remaining compatible": okay, fair enough; I can see that's a reasonable point of view, even though I personally don't agree with it. What I always find off-putting is the casual dismissal of the problems. The Python 3 transition could have been handled better and dependency management remains a bit of a mess in Python: I don't see many Python developers deny these are (or were) real problems.
Private causes extensibility issues (no access to private fields in child classes). Not sure if the article addressed this or not because it's currently hugged to death.
Protected plus subclassing is just poor man's dependency injection; entirely dispensable.
She wants her users to be able to use Proxies over her classes. Private members make a class ineligible for proxying. So she'd rather use private-by-convention.
This effectively and automatically devolves to "public-by-contract". Seen it happen so many times that you can't convince me otherwise.
https://github.com/tc39/proposal-function-implementation-hid...
Whilst I don't personally share all of the concerns posted there and even believe some of them are a little bit silly, I have to say that I do not at all like the way most of the discussions are abruptly cut short or simply left unanswered, and how difficult questions or ideas that aren't entirely in line with the original proposal are immediately declared nonsensical/invalid or shot down without any further exploration at all.
The above is a common theme in that repo and not difficult to find. I've never seen that kind of behaviour in any other proposal at that stage. Very sad to see.
https://github.com/tc39/proposal-function-implementation-hid...
_checks first issue_ yep.
I've stuck with TypeScript private fields which does exactly what I need.
Nothing like having to reimplement dozens upon dozens of methods all because you need one little change in one untouchable private method.
Tell me you don’t know how anything works while not saying you don’t know how anything works.
Js had coped quite well without having private fields. We had conventions of using this._field that served fine, that cautioned users sometbing was internal-ish without binding them, without preventing experimentation.
The private function implementation is more bad language design seeming on an inevitable course to make the language worse & less capable. It's more fear, more adding nothing, more creating friction, like private fields.
That all said, it doesn't seem like proxies ought be broken by private class fields. That's another heap of scorn placed upon this already tragic & harmful misfeature.
There was a huge community backlash against it, which tc39 blithely ignored.
It made me lose confidence in tc39 completely.
Whatever their motivations are, they are clearly not influenced by community feedback, at least not for this feature.
I never understood why TC39 was so intent on this “hard privacy” stance they took, as without it there were much better and more sensible compromises that could have been made. (Especially since JS is an interpreted language - if someone wants to work out what private variables are in your code, they can always just inspect the source.)
Records and tuples for example aren’t controversial. They are primitives and therefore solve a few quirks that come from objects. They should also radically improve performance because users can’t change types or add/remove properties causing less optimized lookups.
Instead, all that time went into a controversial feature that has made things worse without solving any real issues either.
The proxy problem is bigger than the “people don’t respect my authority” problem that led to private fields in the first place.
-- https://en.wikipedia.org/wiki/Self_(programming_language)
> JavaScript is a high-level, often just-in-time compiled language that conforms to the ECMAScript standard. It has dynamic typing, prototype-based object-orientation...
-- https://en.wikipedia.org/wiki/JavaScript
In TS, i default to protected for internal implementation details.
Why would one hinder future extenders by default?
They always have the nuclear option of forking and making those fields no longer internal. They'll have to take on the maintenance overhead that I shirked, but that should be fine if they need it that badly.
while that is always possible, it is not always reasonable.
If you take firefox as the example, the removal of the XUL extension api is one such. And while there's many a forks to keep the old XUL api, none of them really caught on, and so support for them such as maintaining security patches etc fails.
Personally, at least for library code, I tend to assume -somebody- will extend any given class of mine at -some- point and would prefer they had a reasonable time doing so, but I use 'prefer' advisedly since there are absolutely situations where doing so would almost certainly be a footgun and in that case I'd rather dissuade the user before sinking time into the idea.
In C# private is more of suggestion than an enforced concept since you can do anything you want with reflection, which is very useful and necessary sometimes. Keeps normal code clean and encapsulated while allowing one to go under the covers as needed.
Seems very strange that not only can you not get to private members directly in JS but even worse you can't call a public method that use a private though a proxy, bizarre and against what I would think JS to be.
Imagine you have a type that represents some domain state. It has a bunch of fields that are used to represent this state. But that state has invariants. There are certain combinations of values that the fields are not allowed to take and they relate to each other in complex ways. A well designed interface to this type is such that every sequence of methods called on one of these objects will maintain the invariants. If you instead just directly expose the fields then absolutely nothing guarantees that the invariants hold.
Yes, if the first thing you do when you create a private field is write a public getter and a public setter then you haven't gained anything. But the power is in not writing these methods.
Admittedly, not many people know this, so those who are not deeply familiar with TC39 proceedings commonly think of proxies as just a way to hook into arbitrary behaviours of a single object, like property accesses, then are treated to a bitter surprise when proxies don’t work like what they thought or wanted.
Still, the opening example is simply a misuse of Proxy, so it’s rather unconvincing.
That said, with something like <https://github.com/littledan/proposal-new-initialize#another...>, something resembling the popular conception of Proxy might actually be made to work…
The reality is that proxies came first. They see a LOT of use in reactive libraries making it very likely that your classes get passed into one these proxies (and breaking if private fields are used).
They knew all this when ramrodding it through over the many protestors warning about this and other things. The fault is 100% on the hubris of the spec committee.
Only two days ago I chose to rewrite a chunk of code simply because I couldn’t call a private function that did precisely what I needed.
Also “considered harmful” is an overused cliche I wish people would stop saying it.
It may have done what you needed in the current version of your library, but just look at it from the perspective of the library author. If they ever want to refactor, or maybe remove, that private function, it would break a lot of people's code. And even if it were marked private or deprecated, it would still break a lot of people's code. You could say "but if your code gets broken it's your own fault" and yes you're right, but it wouldn't stop those people from loudly complaining to the author.
That's a reasonable commitment to ask for if you're poking around in implementation details.
How are you not taking the burden of maintenance when writing code that uses private-by-convention properties? The difference seems pretty abstract, a fork will require the same rework when internal properties change.
Adding a broken feature that was widely criticized for years (more than any other JS feature before it was added — including type coercion) just so some maintainers don’t have to tell users no about one particular thing isn’t a great idea.
It’s even less of a great idea when you consider that modules exist. Add a variable without marking it for export and you’re good to go.
Javascript modules do not work particularly well as namespaces.
You may `import * as Foo from “foo”` to create a closed namespace.
If I want to create new pieces of data, I have to make an allocation with a constructor. Reusing methods provided by modules is effectively the same as classes (the two being mathematically equivalent, but closures not having all the footguns).
This is true no matter if your code is OOP, imperative, or functional.
Well, in a neighboring comment someone is suggesting making the private stuff local, so it cannot be called as well. So your problem would persist just as well.
Without private methods or some equivalent you won't be able to change your library without breaking lots of people's code.
It also makes understanding the interface hard, if everything is public, how do I know which methods are intended for me to consume, and which are internal helpers.
Of course, not all interfaces will suit your need and some are poorly designed.
A while ago I was looking for a color manipulation library, and I saw her Color.js, and loved all the features. Then I saw the bullet point, “Readable, object-oriented API”. Nope. I’m not touching that. I chose culori instead. It’s just a collection of functions that transform standardized color data objects. It’s like lodash for colors. It’s been great to work with.
This is not true. Private members are a part of a class interface, while local/internal function/variables are not. They both serve totally different purposes. If you're using private members as a substitution to local/internal members, you're doing it in ways that are fundamentally wrong.
I know it sounds bad. Not my fault
Building in code APIs for libraries.
Behavior is the interface/messages supported by the object, and data is an implementation detail that should not leak.
What's wrong with that?
And why are you pinning the blame on a whole paradigm from a problem caused by how a feature was implemented in a language renowned for being fundamentally broken ?
My question is, why is that necessary? Exactly what is the benefit, except for a tiny bit of convenience? Why is that better than functions that work on well-defined types? It adds coupling, and coupling adds complexity, so if I'm adding complexity, I want to know why it's better before I add it.
> And why are you pinning the blame on a whole paradigm from a problem caused by how a feature was implemented in a language renowned for being fundamentally broken
I think calling the JavaScript/TypeScript of today fundamentally broken is clearly untrue. But anyway, I'm not condemning OOP entirely because of this one feature in one language. I thought my comment made clear that I see this as just another in a long line of complexity I've seen that's caused by this unnecessary coupling of data and behavior.
When I think of the OOP paradigm, I really think of Java, which I would call more fundamentally broken than JavaScript. It's improved over the years by pulling in ideas like lambdas that loosen the straightjacket imposed by its broken interpretation of OOP. Half of the revered "design patterns" that we're supposed to learn for OOP are just to work around this coupling when it's getting in the way. Patterns, even if they're repeatable, are complexity. Have you implemented the Visitor Pattern [0] lately? I did it once and I will never do it again. That kind of stuff ties my brain in knots. If you like pointless puzzles, maybe it's fun, but for me it's just more and more complexity. When I just work with data and functions, most things become much simpler.
[0] https://en.wikipedia.org/wiki/Visitor_pattern#/media/File:Vi...
The benefit is subtype polymorphism via dynamic dispatch. When values flow through the program they carry the implementations of their operations with them, so a caller can operate on a value without knowing how that operation is implemented; a newly-introduced value can bring a new implementation of an existing operation, and existing callers can use that implementation without knowing that it’s new. There are other ways of achieving this, but OOP is a good one.
Subtyping/inheritance is sometimes convenient, but inheritance tends to be a constrictive way to reuse code that increases cognitive load. If B is a subtype of A, now, not only are the data of B coupled to the behavior of A, but the data or behavior of B _may or may not_ be coupled to A depending on whether B overrides A. More coupling. More cognitive load. And if you change A, you have to be careful that you don't break B. Then if you have C that's a subtype of B, then the cognitive load and possibility of unexpected breakage gets even higher. Ugh. No. I'm a simple-minded person. I don't want to think about stuff like that.
I see dynamic dispatch simply as a form of delegation, which can easily be accomplished functionally.
So, ultimately, again, I see these benefits as being _convenient_ but not _necessary_. And since all design decisions are trade-offs, I look at the downsides of the coupling and find that the trade-offs are simply not worth it.
Point us to a language that isn't broken in some way, an example of perfection that every language designer should aspire to follow.
And then show us how that perfection has helped that language to take over the computing world.
Caches which require specific invalidation logic.
A good mental model is the query language for a database. The language provides a frontdoor for all the state, so you can't just poke around and mutate things.
I'm not sure where ORMs entered the picture, but my point is that the database uses the query engine to guard internal caches which are private via the process boundary.
OOP is a technique which shouldn't prescribed for everything. I will say Javascript shouldn't have have classes, but I hope to never need to use JS much beyond glue.
The problem is more one of, you don't want everything to be a state machine, and adding the boilerplate tends to overcouple the data. The maximally composable form will tend to extract a little FSM with one variable, while all other relevant data is simply passed into every method.
-- https://en.wikipedia.org/wiki/JavaScript
There’s also a case that using those methods is indistinguishable from something like typeclasses which don’t involve classes at all.
If we want to be even more pedantic, JS is prototypal and fundamentally lacks classes with objects simply having an optional pointer to B other objects, so the implementation of these methods doesn’t necessarily imply classes.
You also have an argument that proves too much. If this argument is true, then your previous multiparadigm claim is proven false.
In any case, it doesn’t seem like a good faith argument. There’s no reason I ever have to make my own classes in order to make projects in JavaScript. This is all a typical programmer is going to care about and is the spirit of my statement.
OOP isn't classes, I suggest education on OOP type theory, and regarding type classes, a few Symon Peyton Jones papers on how type classes in FP relate to OOP.
ACM and SIGPLAN.
Compilers > VDOMs + runtimes
The spec committee gave the community the middle finger and did it anyway.
Now we’re stuck with yet another giant footgun from their hubris.
Because it’s so new, maybe they should just consider removing it from the spec.
It's borederline clickbait, or selfbait, as the author believes they've contributed to the ever shuffling stinky pile of hot takes that exist in the web world.
It's really not as exciting to say Proxy, a niche class, than saying there's a problem with a concept of something every day, like private fields.
I doubt the vast majority of you have any experiene with Proxy, but OOP? Im sure you've brooded on a few hottakes about that in your careers.
JS has modules that offer variable privacy that doesn’t cause all the issues of private fields.
Apologies in advance if I'm misunderstanding the purpose of proxies.