
JavaScript TC39 implementing hashmark private class fields - just_astounded
https://github.com/tc39/proposal-class-fields/issues/15#issuecomment-319877880
======
agentultra
The keywords were reserved in the latest spec for just this purpose.

I don't think the rationale for their inclusion is a good one. The inclusion
of such keywords in languages like C++, Java, etc was well-meaning but a
mistake in retrospect. In the presence of global variables and direct
assignment these keywords are the PHB approach to information hiding. They
require ceremony and diligence to achieve true encapsulation. It's a murky
situation at best.

See Bob Martin
[https://www.youtube.com/watch?v=TMuno5RZNeE&t=2325](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2325)
and Bertrand Meyer on Eiffel:
[http://se.inf.ethz.ch/old/teaching/ss2007/0050/slides/03_sof...](http://se.inf.ethz.ch/old/teaching/ss2007/0050/slides/03_softarch_info_hiding_lang_constr_3up.pdf)
(Bertrand Meyer coined the Open/Closed Principle and is the developer behind
Eiffel).

At this point though I see their inclusion in JS being almost inevitable. Just
another feature to ignore.

It'd be nicer if they would look to add syntax for higher-order operators like
_compose_ to the language instead of this.

------
blargathon
Kinda disappointing. I don't really like the # for privates. It's not an
elegant solution. It feels a bit like a one-off hack honestly.

~~~
jwalton
Right now I'm prefixing variables I don't want external actors to change with
underscores, which doesn't actually do anything, so is arguably more of a
hack. :P

~~~
BinaryIdiot
You could encapsulate them with a closure. They don't _need_ to be exposed.

~~~
cromwellian
Does this scale in a large code base? What's the effect on memory and
performance of hiding most of your fields by closures?

~~~
hajile
Closures in JS are basically objects that have only one public property
(called apply).

Modern JITs are good at optimizing static objects (provided props are never
added or deleted and the types never change). There is still (from what I
remember) a small performance increase to normal objects, but it only becomes
significant if you are creating hundreds or thousands of them.

I'd say that in typical cases though that closures should be faster because
devs tend to use them in ways that are easier to optimize (Closures don't ever
add/remove properties and types are much more likely to stay the same).

~~~
barleymash
> Closures in JS are basically objects that have only one public property
> (called apply).

Never heard it explained this way. Can you elaborate a bit? I'm not sure I
follow. Couldn't a closure have any of the same properties that other
functions have? Call, apply, bind, length, etc, etc.

~~~
hajile
When you execute a function, you set the program to execute the new function,
but first, you take care of the related bookkeeping. A very important part of
that is the function scope(s).

I'll first explain prototypical inheritance (because the two have close
parallels). When you access a property in an object, a method runs behind the
scenes. It does something like: search all the keys in the given object. If
there is a matching key, return the value. If no such key exists, check for a
__proto__ key. If it contains a value, call this function recursively on that
object (and return whatever it gives back). If there is no __proto__ value,
return `undefined`.

Let's assume an interpreter (to make it easy). Before we call a function, we
need to setup the closure. The closure is an object with the names of all the
variables you defined plus a few builtin things.

As we parse the function, any params, `var` or `function` statement creates an
entries in the object. The values for the params are then pre-defined from the
stuff provided by the caller. All the function statements are also pre-
compiled. We add internal values for the return value, the parent closure
(we'll call it __closure__), and some other things.

As the function runs, we come across a variable name. We then call a method to
get it. That method searches the current scope for the name and returns the
value. If none is found, it returns the result of calling itself on the
__closure__ object. If no such object exists, it returns a `ReferenceError`.

Modern JITs do many fancy things with closures (just like they do with
objects), but this basic mental model should cover most things.

------
svckr
Wouldn't it be funny if we see a resurgence of compile-to-js languages? If
JavaScript keeps on piling up layers I could see people longing for a simpler,
smaller language.

Where there was coffeescript to highlight functional features in JS in a time
where most JS was imperative, maybe we'll see a language that takes the OO
sugar away to, again, reveal the functional language that's underneath.

~~~
erikpukinskis
I am absolutely planning on forking an old school JavaScript off of the
current runtimes. Or at least forking NPM. Not soon, because for now writing
ES5 and running it on ES6 runtimes is Good Enough for my current purposes, as
I'm still <1.0.0 so nothing really matters. But eventually I'm going to want a
separate package space, and a separate language community.

Modern JavaScript is cool, but it loses many of the benefits of old school JS:

\- only runs on newer devices

\- requires a precompile step

\- more language to learn

\- confusing concurrency model

\- confusing inheritance mechanisms

\- no incentive to learn to use closures, callbacks, and prototypes properly

If I was willing to accept all of those things, I would just use a straight up
better language, like Rust or Haskell, and get the additional benefits of type
checking and deterministic performance.

But I'm not interested in those things. I want a simple run-everywhere
language for beginners to get started on. JavaScript used to be that, but
modern JavaScript is not that language anymore.

So, I'm going to bring back ES5. Why not. Who's with me?

~~~
just_astounded
I remember when Crockford released the fantastic book, JavaScript The Good
Parts. I disagreed with some of his opinions in that book, like avoiding the
use of 'new', but that book - more than almost anything else, helped me
educate folks on how to use js well. I used to order copies for every member
of my team. I wonder if, instead of a fork, we can just make a case for a
subset of js. Take the good new things, and ignore the rest, like a modern
"Good Parts".

~~~
mercer
Could you elaborate on the things you disagreed with? For example, for some
reason I prefer avoiding new but I'm conflicted about it. I'd really like to
hear your input as to why perhaps using 'new' is a good thing.

~~~
erikpukinskis
Not OP, but new is dangerous because it makes it easy to put a lot of methods
on an object, which is generally an anti-pattern.

But sometimes that's what you want. I think a good use case is view models...
You're not mutating anything, but you have a large number of consumers that
are using different subsets of views on a piece of data. Constructing those by
hand can be a PITA.

Although, it's dangerous there. "Lots of views on a piece of data" might be a
way of covering up "several disparate pieces of data in one place" or "data
that is playing two roles when it should be transmuted instead". So, it's good
to be afraid of new.

I am still frequently decomposing a single index of objects into separate
indexes of literals fairly often. I often get sucked into thinking something
is an object when it's really just a few separate pieces of data indexed
together.

I also use new for libraries that export something that isn't exactly a
function, but more a point of reference. For example my browser-bridge module
is a point of reference for the computation between a web request and
response. It's not really an action, but it's a thin representation over a
handful of low level things you want to do in that space. It's purpose is to
wrap up a set of concerns.

Essentially, OO is generally bad in JavaScript, but sometimes it's good and in
those cases new is nice.

------
jorblumesea
True "Classes" in JS always seemed like a hack anyways. It will never be true
OOP, so why shoehorn these concepts into it? Especially given prototypical
inheritance patterns we will never have real java style oop patterns. Except
for primitives, almost everything is an object, but they are basically
hashmaps, not real classes or objects as someone from other languages might
think of them.

Imo, the language was not build or designed for some of these heavier OOP
concepts. Just my opinion.

This seems to fly in the face of many of the great things ES6 implemented.

~~~
Andrex
You don't need all aspects of OOP ever to get use out of some of its concepts.
ES2015 classes are much cleaner and easier for newbies to grok than messing
with the prototype chain, which was the way that sort of thing was achieved
before.

Classes also make a lot of sense for custom elements (Web Components.) See
Polymer v1 compared to v2.

~~~
erikpukinskis
ES2015 classes are not easier to learn for newbies, they're easier to learn
for Rubyists and other people coming from OO languages.

There's nothing hard about this:

    
    
        function Person(name) {
          this.name = name
        }
    
        Person.prototype.greet = function() {
          return "Hi, "+this.name
        }
    
        var me = new Person("Erik")
        console.log(me.greet())
    

The syntax hurts your brain if you're used to looking at something else, but
the control flow is actually very simple and totally transparent, unlike ES6,
which does magical things that can't be understood by thinking about where the
thread is moving.

~~~
ubertaco
Which is easier, explaining to your hypothetical total beginner what
"Person.prototype" is and going down the rabbit hole of prototype-inheritance,
and explaining how Javascript functions (unlike just about every other
language's functions) can have attributes on them and can act like fake
constructors and what "this" means in your example (and then later what it
means in a different case, since it means so many different things)...or
simply saying "yeah, we can define a new category, or 'class', of things
called Person by saying 'class Person { ....'"?

~~~
erikpukinskis
You only think "class" is self explanatory because you already learned what it
means.

~~~
ubertaco
Yes, but plenty of other non-programmers also learned what it means, when they
first heard the English word. So it's not a huge conceptual leap.

Whereas "prototype" has completely different English dictionary meaning, and
thus requires in-depth redefinition.

------
watty
Seems like a pretty fair and well though out solution after reading the latest
response by bakkot.

~~~
bcherny
I read through the thread, and the proposal sounds crazy to me.

1\. Members marked private being accessible from other instances of the same
class [1] is counter to every other OO language I have used. What is the prior
art here? Why depart from all conventions like this?

2\. The # syntax is not extensible. If TC39 decides to add protected,
internal, etc. modifiers in the future, what will they look like? We should be
looking to TypeScript's private/protected modifiers as a battle tested
solution that fits the existing convention set by the "static" modifier.

\---

[1] [https://github.com/tc39/proposal-class-
fields/issues/15#issu...](https://github.com/tc39/proposal-class-
fields/issues/15#issuecomment-319964681)

~~~
JoshTriplett
> 1\. Members marked private being accessible from other instances of the same
> class [1] is counter to every other OO language I have used. What is the
> prior art here? Why depart from all conventions like this?

C++ works that way, and several other languages do as well. And the use case
mentioned in that comment is exactly the reason: the implementation of a class
knows how to access both its own private fields and those of another instance
of itself, so that it can do comparisons, combinations, or other operations.

~~~
spankalee
It gets really tricky when you have any kind of polymorphism, intended or not.

If you accept instances of your own class as arguments to a method, you may be
templated to think you can access the private fields of those arguments, even
if instanceof checks pass, but they may not be there.

------
pavlov
A noteworthy feature of JavaScript is that it is predominantly a scripting
language for large OOP-designed C++ systems. All the major browsers are C++
underneath, and so is Node.

This has created an odd tension where the APIs that programmers use are
implemented as OOP under the hood, yet JavaScript itself shied away from OOP
concepts. With features like this, the JS standard is slowly creeping towards
the style of programming that underlies the ecosystem.

------
dyeje
Can someone provide some context? This link just drops you into the middle of
the argument.

~~~
wavefunction
Some in the JavaScript community have been asking for private members as a
language feature and some folks do not want this as there is a subset of
JavaScript programmers who worry about many concepts from OOP "polluting" the
relative minimalism of JavaScript. A fear I share to a certain extent.

From reading the thread it sounds like some feel that the addition of private
members is being accomplished by fiat rather than community consensus.

~~~
mcphage
> From reading the thread it sounds like some feel that the addition of
> private members is being accomplished by fiat rather than community
> consensus.

There's a difference between "community consensus" and "consensus of the
people posting comments on this github issue".

~~~
hajile
What evidence is there for the "silent majority"? There's no good way to
collect that kind of data (after all, they're silent). It sounds like a
baseless statement of "fact" to bolster an otherwise losing position.

~~~
jpambrun
Look at the harsh responses that we can already see on HN and you will
understand why I usually prefer to remain silent on such topic. A lot JS devs
seem to feel very strongly about any kind of change.

------
frou_dh
There's a cute use of WeakMap to do private fields in a GC-friendly way

    
    
        let ages = new WeakMap()
    
        export class Person {
          constructor(name, age) {
            this.name = name
            ages.set(this, age)
          }
    
          // ages.get(this)
        }

------
skue
Background for this proposal and discussion can be found in this repo’s README
and additional docs:

[https://github.com/tc39/proposal-private-
fields](https://github.com/tc39/proposal-private-fields)

------
simplify
Why are private properties so sorely needed? Ruby is an OOP language, and gets
along just fine without it.

~~~
dwb
You can declare visibility in Ruby:
[https://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Classe...](https://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Classes#Declaring_Visibility)

It can usually (always?) be gotten around though.

~~~
simplify
Right. You can say something's "private", but there's always a way to access
it. As far as I'm aware, this has never been an actual problem, and in fact
has been _useful_ since you can "patch" a library if you really need to.

------
model_m_warrior
I honestly do not see the need for these bloated features being added to Js
just to appease programmers who refuse to learn a differing methodology.

------
pier25
Personally I'm not against classes, if someone prefers to use classes instead
of prototype it's fine by me.

What I don't understand is why the TC39 is pushing for classes and classic OOP
instead of other more pressing issues. You can accomplish OOP with prototypes.

IMO the lack of type checking is much more problematic. Flow and TypeScript
are just adding a new layer of problems to an already convoluted workflow.

~~~
stupidcar
You realise JS classes are syntactic sugar for prototypes, right?

~~~
pier25
That's precisely my point.

Classes shouldn't be a priority.

------
rurban
I find it extremely amusing that the once clear JavaScript turns into ugly
Perl, whilst Perl thrives to clear its ugly sigils and starts using ascii
keywords for such properties. Design by committee rears its ugly heads

------
calafrax
I always wonder why if people need these features so badly they don't just
program in Java or C++?

~~~
pjmlp
Because currently they aren't supported on the browser.

But thanks to WebAssembly they will be eventually there again.

------
rocky1138
This is the primary reason I didn't like the idea of adding "classes" to
JavaScript in the first place. Classical OOP doesn't really have a place here
and all it does is invite the wrong type of thinking into the language.

~~~
just_astounded
Agreed. We already had solutions for inheritance, etc... using prototypes,
which are more powerful than classic OOP. Now we're in sort of a mess hybrid
thing where issues like this are mostly unsolvable in a clean way.

~~~
stupidcar
JS classes build on prototypal inheritance, they aren't an alternative
mechanism. So you apparently either don't understand them, don't understand
"classic OOP", or both.

~~~
just_astounded
Yeah, I've only been doing OOP for about 22 years, so I'm pretty new to it.

The point was that the syntactic sugar isn't needed.

The 'class' sugar actually obscures the power of prototypal inheritance, and
imho, discourages people from learning it - to their, and the language's,
detriment.

~~~
camus2
> The point was that the syntactic sugar isn't needed.

Yes it is. The success of CoffeeSCript is absolutely a validation that
developers are more productive with syntactic sugar such as the class keyword,
destructuring and many many other features.

Now nobody forces you to use them. So why complain? It doesn't make Javascript
harder to read for you, quite the contrary.

~~~
just_astounded
> So why complain?

So it is your opinion that the community and users shouldn't give feedback on
the design and direction of the language they use every day? That's an
interesting approach to openness.

