Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login
Classes Are Making Me Sad (gist.github.com) similar stories update story
53 points by acconrad | karma 7709 | avg karma 5.32 2017-03-08 07:28:32 | hide | past | favorite | 62 comments



view as:

Javascript is making you sad.

Javascript is making you sad.

Whoever at Netscape vetoed Scheme as the browser language should be on trial in The Hague.

They were in an uncomfortable position.

They had to add scripting capabilities, and be first at it (else MS would be the first). They had to make the language instantly understandable by an average programmer — that is, someone who has a Basic / Pascal / C / Java / Perl background, and an hour of time to get acquainted and like the language.

So they created a scripting language with a familiar C-like syntax, very few restraints, and a bunch of "convenient" magic that helped a person to create a simple animation in the browser on their very first day, by mashing things together.

This was a terrible strategy to create a nice language for safe and comfortable development of large applications, especially full desktop applications 20 years from the inception date.

It was a very well-working strategy to get mass adoption in no time, and own the market the same year in the face of mighty competition. This is what business cared about, and this allowed Netscape to stay successful for a few more years.

Alas.

Now we have WebAssembly (which resembles a stripped-down JVM from 25 years ago), and soon will be able to efficiently compile our favorite "real" language to run in the browser not through JavaScript.



The short story has it that it was some Java-pushing dickheads from Sun Microsystems.

You may dislike my tone, but it is basically factual.

https://news.ycombinator.com/item?id=9098367

Eich> I joined Netscape on April 4, 1995, to "do Scheme in the browser". Immediately I was out of luck on several fronts:

Eich> [...]

Eich> If there was to be a "scripting language", it had to look like Java.

https://news.ycombinator.com/item?id=2786720

Eich> Remember, I was recruited to "do Scheme", which felt like bait and switch in light of the Java deal brewing by the time I joined Netscape.


Cool. This explains why the better I understood Clojure, the more my Javascript started to look lispy.

Having used JS along with somewhere north of 10 other languages over the years... JS is the most painful, even moreso than Perl which was close.

Classes and objects are 100x better than the crap they've come up with to fix JS so far so stop complaining.

Oh no it's not functional???!!! Javascript never was. It was always supposed to be an OO language the implementation is just shit. For the love of God everyone just shut up and let them fix it. Trying to change JS from what it was meant to be into something else entirely is just going to make things even worse


Dude classes are making you sad? Man, you should shield up, _a lot_ of things beyond classes are going to make you sad. Classes and javascript are a history between 2 different clans fighting together and no one winning, but it's still javascript. Not a huge deal if it's shittier than it already is. I like the language don't get me wrong, but there is so much in there that's unpleasant, that another construct shouldn't affect you much.

As someone who works mostly in React I've never found a use for classes. It's a contentious, messy feature that solves problems I don't have.

I used Coffeescript classes in the old jQuery/Backbone days and they were useful only because the rest of the ecosystem was so terrible.


> As someone who works mostly in React I've never found a use for classes.

You can't NOT use classes if you use React. [Edit: For components with state] your syntax choices are:

    Foo = React.createClass({})
Or:

    class Foo extends React.Component {}
But both are examples of JS classes; it's just a question of whether you use the class keyword or not. Every React component [Edit: with state] is a class.

Edit: Fixed silly error; obviously you can use stateless functional components and I do all the time, but you can't avoid classes with React.


I tend to heavily favor functional components[1]:

    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
or a little nicer:

    const Welcome = ({ name }) => (
      <h1>Hello, {name}</h1>
    )

[1]: https://facebook.github.io/react/docs/components-and-props.h...

Sorry, typed too fast. Edited my answer to clarify.

well, no.

        const Foo = props => <div {...props}/>
Is a valid "stateless component". They are just the render function.

Sure, I do define classes but that's because React expects it -- I don't actually use them as classes. My React classes would function the same as plain old objects. I never use constructors, or inheritence, or new, or any of the features mentioned in this post.

Try Elm?

I am missing Go, Rust and Haskell in this thread.

And since you reply on a comment mentioning Elm, I think PureScript (feature-wise it's more Haskell-like, also more general purpose) deserves a mention as well.

http://www.purescript.org/


Not OP, but I just finished a small trial phase with it.

There's no straightforward way to make nested record updates. You have to write separate setter functions for every step of the way.

If you don't list every single mutation in a single type, and have a single update function to handle them all, you're in for an exhorbitant amount of boilerplate to wire it all. Long, single file types and functions with Objective-C style namespacing are idiomatic.

All is good error wise, until you have to use the escape hatch to JavaScript, in which case good luck building a separate castle of abstractions to fit both of them together and debug it.

There's also the fact that the language creator has to bless a set of bindings before they're allowed to be packaged and uploaded. I do not know how this works out in practice, but it certainly made me raise my eyebrows and not in a good way.

In summary: It's a promising language, but using it to build large codebases either involves techniques the community doesn't know about or it's an exercise in masochism. Maybe in a few years it'll be good.


Did you have a look at PureScript? It has type classes, which might just elegantly solve your boilerplate problems. I think it's FFI is also simpler.

I have, and ignored it. It appears to be too focused on developers already familiar with pure functional programming and the documentation looks impenetrable; where Elm does much better on both fronts.

I might take a deeper look at it later, however. But I'm not interested in making a web application if I can't train a JavaScript programmer to be productive with it in a week. Elm did give me that, and I get the impression PureScript doesn't.


> There's no straightforward way to make nested record updates. You have to write separate setter functions for every step of the way.

A pattern for this kind of thing in Haskell is Lenses/Prisms: http://adit.io/posts/2013-07-22-lenses-in-pictures.html


Thanks for the insight; we're also evaluating Elm, but our code base isn't large, and we committed to using flat records. Good point about the nested record handling.

For what it's worth, I am analyzing it again after making a small prototype of the application I needed it for but using Vue + Vuex. It's helped me put in perspective the amount of boilerplate needed by Elm, and now I'm not so sure that particular downside is as big compared to the alternatives as to disqualify it...

I'm going to give it another full week before going one way or the other.


How much energy will be spent thrusting this regressive pig?

More than will be spent thrusting all other barnyard animals combined?

Every time I think I understand Javascript, I read something about its prototypes and constructors and how 'new' and 'this' works and I am lost again.

The thing I like about classes is I vaguely understand what is going on and why, coming from Java and similar. So I am using them, together with Flow typechecking.

I am not saying the javascript prototype logic is bad, just that I never understood it, even when I work in Javascript for years.

Sometimes I understand, for a brief moment, "how", but never "why".



Yeah, I always understand "how" from books like this, but not "why". Why is javascript like that?

More to the point, what is the reason for prototype-based inheritance, what is its benefit, what more can you do with it than with C++/java, where does it make life easier.


I would encourage you to read https://github.com/getify/You-Dont-Know-JS (ideally all of it, but start by doing searches for 'class', 'prototype', etc.)

Edit: Funny to see someone else post a link to the same book at the same time :)


> The thing I like about classes is I vaguely understand what is going on [...] I am not saying the javascript prototype logic is bad, just that I never understood it, even when I work in Javascript for years.

But by using classes, you're still using prototypes, it just isn't as immediately apparent.


Sure, and by using classes in C++, you're still using tables of functions pointer like you can in C. The "not as immediately apparent" part is where the usability improvement comes from. It means you don't need to think about the machinery as much. You can work at a higher level of abstraction.

(Of course, the details of how the feature is designed determine how leaky the abstraction is and how often you actually get the luxury of not thinking about the machinery.)


I think understanding "this" in javascript is actually much easier if you have had exposure to C++ function tables.

With that knowledge in hand, it immediately becomes obvious that:

  bar.baz = function(){console.log(this.X)}
  var foo = bar.baz;
  foo();
Is not going to result in a console log of bar.X. "foo" is a pointer-to-member function, and executing it without a context is going to cause problems. The biggest in javascript as compared to c++ is that javascript will always provide the hidden "this" (even if it's the global context), whereas c++ will always bail out if you don't provide it with a proper context.

But really, understanding that behind the scenes, member function calls change:

  A.B(C)

  into

  B(A, C)
clears up a lot of "this" mistakes, and also enlightens the proper usage of function.bind.

Once I started using arrow notations, I stopped worrying about `this`.

Whenever I need to export class function, I do

    var foo = () => bar.baz()
, no more worrying, no need to understand what the hell bind does exactly.

Also with the ... notation, I stopped worrying about .call and .apply. So I can write

    var someArray = [a,b,c]
    var foo = () => bar.baz(...someArray)
and so on.

> Every time I think I understand Javascript, I read something about its prototypes and constructors and how 'new' and 'this' works and I am lost again.

Prototypes, constructors, and context are not that complicated; I encourage you to keep reading and trying things. You may be surprised how quickly you end up "getting" it.

> The thing I like about classes is I vaguely understand what is going on and why, coming from Java and similar.

No. The class keyword in JS is syntactic sugar around the same prototype stuff that already existed. It does not work like it does in Java, and you will not understand what it's doing unless you understand how the class keyword translates directly into manipulating the prototype chain.

Which, as above, is easy! But if there's one actual problem with the class keyword it's that it makes people who know Java think they understand JS inheritance.

The class keyword DOES NOT CHANGE YOUR CODE. If you didn't understand what was going on before you used the class keyword, you don't understand it after adding it in. (Conversely, if you actually do understand how the class keyword works, then rejoice, you now understand how prototypes work...)


>Which, as above, is easy! But if there's one actual problem with the class keyword it's that it makes people who know Java think they understand JS inheritance.

Which is totally fine, as for all intends and purposes the behavior is the same. You create new instances, and you call methods on them.

The "movable" context (this) will be a little surprising, but for everything else one can write JS for decades with ES6 classes and "OOP", without having to know how the prototypes, objects and functions they de-sugar to work.


> Which is totally fine, as for all intends and purposes the behavior is the same.

Nope :) They're apparently the same, which is not the same as "the same".

> The "movable" context (this) will be a little surprising, but for everything else one can write JS for decades with ES6 classes and "OOP", without having to know how the prototypes, objects and functions they de-sugar to work.

That's just the thing though - there isn't a "movable context", there is only binding. Binding usually happens implicitly, and most of the time things are implicitly bound to what you expect them to be if you're not aware it's happening.

... then comes the moment that you're in an instance's method, and `this` is pointing to an event instead of the object. Oh, boy.

In my experience that moment comes much more often than once every few decades.


My understanding about the how:

- 'this' is based on how you call something, not what it is attached to. It's possible to have a.fn and b.fn be the same function with 'this' referring to each object in turn when you call a.fn() and b.fn().

- Function.bind muddies the waters a little bit, but basically that creates a wrapper that forces 'this' to a particular object.

- a 'class' in js is a function with a prototype property pointing to a parent object / parent class instance (not a class)

- new fn() creates an object, assigns the prototype, and runs fn.call(obj)

- obj.prop walks the chain of prototypes attached to obj until it finds 'prop' on one of the instances

As to the why, the idea is that the core object is the function, not the class, and prototypes are a way of conveniently determining the context a function is executed in. There is no such thing as a class in js, only a class-like syntax to wrangle functions and objects.


Is there a a reason to learn bind, where I can use arrow function, which is cleaner and more readable? (And probably does bind under the hood)

Also, what is the reason for the prototype style inheritance? What is the benefit? Why use it over "classic", java-style OOP?


In prototype inheritance, objects inherit from objects. This creates flexibility, since you can dynamically create a base object to inherit from. You can also easily augment base objects after they're inherited from. Class-based inheritance is less powerful in most languages due to the increased runtime rigidity of the class hierarchy. That's the theory. In practice everyone treats prototypes as classes.

> Is there a a reason to learn bind, where I can use arrow function, which is cleaner and more readable? (And probably does bind under the hood)

Off the top of my head, arrow functions come in handy when you're defining an "instance method" that you want to use as an event handler; you want `this` to refer to the instance, not the event. That's 95% of the time you'd use an explicit call to `bind` anyhow.

Another case to use `bind` would be a generic function that isn't specific to the type of object you're acting upon. I wrote up a short example on jsfiddle: https://jsfiddle.net/LyndsySimon/5zqtjd82/

You can also use binding to create decorators that work on instance methods: https://jsfiddle.net/LyndsySimon/n57or0u5/


Which is the problem with "class" in a nutshell.

If you don't understand prototypal inheritance in ES5, you don't use it, and you stick with simpler concepts that work in a clean and elegant way that doesn't depend on OO-like trappings cluttering up your code unnecessarily.

But with that "class" keyword sitting there, it looks vaguely familiar enough, and you just start programming as if it were Java, and your code turns into this complex morass without you even realizing it until the pain starts.


> Which is the problem with "class" in a nutshell.

Which is not new with Javascript in general. every single feature in javascript suffers from being strangely familiar while having a behavior specific to Javascript. Comparison operators, var declaration hoisting,function declaration hoisting, "this", "typeof" operators,type equality and coercion rules, and co, all require to actually learn how the language works. This isn't a very good argument against the class keyword. Yes, developers who use a language need to learn it before hand. It applies to all languages.


There isn't really a need for classes...

If only ES were more like E or Monte, where there are no classes and no `this`.

I'd love if we could have a better conversation about JavaScript than "the language is bad and if you like it in any way you should feel bad" but seemingly not today.

To create a bit to the discussion, I feel like the class abstraction in ES2015 is mostly a mask that doesn't do people any favors - it makes one think they know what is happening but leads experienced programmers to false reasoning and much head banging. I've highly preferred a more functional style for most of my projects and teams - I wish there were better standard tools for enforcing style, as organizationally it's harder to get everyone on the same page than with more mature OOP tooling (which OOP patterns/strong typing makes easier to build).


Seems like you kind of agree it is bad.

Really, the largest problem I see in javascript is exactly how it doesn't have a lot of features to enforce style.

Sure, there is some other wonky stuff, but the ease with which one can write bad javascript is the core issue.


This is a relatively solved problem with a proper .eslintrc and a style guide. You can pick two of the most popular -- airbnb or standardjs, npm install their eslint rules, and go on your merry way. Rolling your own and/or extending these is relatively simple.

> I feel like the class abstraction in ES2015 is mostly a mask that doesn't do people any favors

I agree completely.

When I first read the ES2015 draft, I was excited to see the class syntax. I assumed that it meant that JS was getting "traditional" inheritance, and it seems like a lot of others did too. That illusion was shattered shortly after beginning to use it.

Now, instead of thinking "Hey, these weird objects that I barely understand how I created don't behave as I expect! Javascript is different.", I think a lot of less experienced developers end up thinking "Hey, these objects don't behave as I expect. Javascript sucks."


> Classes Are Making Me Sad

And they are in the spec. It's too late to be sad about that, people are going to use them and they should.


JavaScript is not an object-oriented language, but a prototypical one. JS "classes" are really an attempt to use syntactic sugar to beat one concept into another. This is inherently ugly.

For anyone convinced they hate JS, I encourage looking through Crockford's JavaScript: The Good Parts, in which he talks about the idea mentioned above, before swearing off JS forever.


> JavaScript is not an object-oriented language, but a prototypical

Prototypical languages are a subset of OO languages, despite the fact the the industrial popularity of a handful of class-based OO languages (especially C++ and it's descendants) starting in the late 1980s have made it so that many people confuse OO with specifically class-based OO.


The first object-oriented languages—Simula and Smalltalk—were class-based, so when "object-oriented programming" was coined, it implicitly included classes. Every OOP language had classes.

Only later when Self came out was the term broadened to include languages oriented around objects without classes.

Sure, we need to be more precise today and refer to "class-based object-oriented" and "prototypal object-oriented", but that's not a fault of the class-based languages. They laid claim to the general term first.

Before horseless carriages, all carriages had horses, so it seems unreasonable to expect people to have presciently called them "horseful carriages" even before automobiles were invented.


http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay...

"OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things."

Simula has classes, but Alan Kay's "object oriented" wasn't about classes at all.


> JavaScript is not an object-oriented language, but a prototypical one.

Both Python and Ruby classes are implemented using prototypes. It doesn't make these languages less object oriented.

Prototypes are an implementation detail, they have a single goal: inheritance thanks to the prototype chain.

The fact is, objects are constructs that carry both behavior and state, in that sense JavaScript is capable of object oriented programming. And it has first class functions, which makes possible implementing some functional programming patterns.

Therefore

> JavaScript is not an object-oriented language

is a false statement.


Wonder what the language would now look like if we had some dedicated syntax for Object.create instead of hiding the underlying prototype mechanism under the familiar class syntax.

    typeof (class {}) === "function"
This is almost guaranteed to be a future WAT[1] moment for the next generation of JavaScript programmers.

I can already see that a big part of the appeal for classes is that they keep your code looking neat and succinct (as opposed to throwing .prototype and Object.create all over the place.

I'd much rather be writing code that looked something like the below, than having to think about super, static, own properties, constructors, private fields etc.

   // provided by some view library
   let Component = {
     state: null,

     setState(state) {
       Object.assign(this.state, state);
       this.forceUpdate(); 
     }

     // ...
   }

   let Counter is Component {
     get state() {
       return { count: 0 };
     },

     inc() {
       let { count } = this.state;
       let { step } = this.props;
       this.setState({ count: count + step });
     },

     render() {
       let { count } = this.state;
       return <button onClick={this.inc}>{count}</button>;
     }
   }

   render(<Counter step={2} />);
Although it introduces a new keyword, the abstraction ends up being minimal, because it's just a sugar for the existing Object.create.

   // let Counter is Component
   let Counter = Object.create(Component);
This is obviously a very half baked idea, but I have programmed quite happily with Object.create for a long time without ever _needing_ to use classes. If we had better tools for working with prototypes then maybe we wouldn't have to pretend the language had classes instead.

[1]: https://www.destroyallsoftware.com/talks/wat


Me I am sad about how unreliable comparison operators are.

We fix equality by using === instead of ==. == is so bad that it is not transitive if a==b && b==c you are not sure c==a

And everytime we use <, <=, >, >=, cmp we are back at using == implicitly, which triggers so many problems and bugs (and sort relies on them, thus sort is also broken).

If a language cannot do simple math correctly, it should never be used for stuffs related to commercial transactions or security... anything serious.


I always thought that Javascript kind of is modern Lisp, apart from all the parens. Radical freedom means everybody implements their own classes, promises, all kind of stuff that is out of the box in other languages — which makes us happy because we can play with it, but then makes everybody unhappy when we try to bring our toys together. And when somebody gets their way in deciding what is the _right_way_ to play, all the rest of us get even more unhappy.

Comunism for you then. Let's end the class struggle!

PS: This comment does not intend to be productive in any way.


Legal | privacy