I started to get a bad feeling when the class keyword became popular, but all of the old good parts are still there.
I started to get a bad feeling when the class keyword became popular, but all of the old good parts are still there.
1. Particularly in pure JS projects which either haven’t yet or won’t migrate to TypeScript, classes are an excellent way to define data types. They needn’t be stateful, they can be used just like POJOs in otherwise pure functional code. But their shape is clear (or can/should be), and can be used as both types and runtime references without a lot of ceremony.
2. Classes, when they have a clear shape, are great for performance-sensitive code. POJOs can achieve this, but it’s frequently all too easy for their shape to evolve as a codebase is in maintenance. And that can cause hard to find performance regressions.
That `class` syntax works in all the modern browsers today, no Typescript transpile or Babel plugin or "polyfill" needed.
(Dojo comes to mind because of doing code on current ArcGIS-related projects that are still stuck in the mines of ancient Dojo versions, IIFE "classes", and AMD wrappers. Still great to have Typescript generate the AMD wrappers for you from modern ES2015 module syntax, but `class` and even `await/async` you can get for "free" in modern browsers and are huge clarifying changes to legacy Dojo work.)
- Constructing flat POJO values is trivial (spread + define any changed properties).
- Immutable array operations are fairly robust, albeit not as expressive as some may want. Getting comfortable with reduce improves this dramatically.
- Map/Set constructors accept instances to create new values.
- Class instances used as value types are simple to derive, assuming their constructors accept objects in their shape. If they have a consistent shape, deeply nested clones with consistent descendant classes are equally trivial.
- Iterators could play nicer with other native interfaces, but they’re pretty handy even so. Custom iterators are exceedingly handy for shuttling data between types.
- More expressive operations over sequence-like structures is as simple as operating on iterators. This will feel more reasonable if you favor Map over POJO for mapped types.
- Imperative stateful functions are admittedly a pain, but wrapping them in functions which return meaningful state you’ll need later is pretty straightforward.
- Really though, starting from a functional perspective just makes these details a non-issue. If your primary tool is a pure function, any gap in these interfaces can be filled by writing a function and calling it rather than the imperative code.
If some or all of that sounds like it’s a performance nightmare… most of the time it isn’t. But if you have a hot loop where copy on write is a meaningful bottleneck:
- You can take inspiration from Clojure or other functional languages with persistent data structures. There are a ton of libraries.
- You can take inspiration from Clojure or other functional languages with a concept like transients, and isolate your mutable code in a function body. This doesn’t even need a library, it’s just bailing to the imperative code the language ostensibly wants you to write in the first place, and being thoughtful about how it’s encapsulated.
What I do find painful and tedious is trying to follow stateful imperative code which hasn’t been subjected to intense discipline. This is true regardless of the language. It’s true whether the code is “object oriented” or not.
Your list sums up why I find it tedious ;), since you mention Clojure, in Clojure one doesn't have to think about those things and go through hoops(except for when doing Java interop). Unfortunately I don't think Clojurescript is worth the hassle(or even immutablejs or immer) and just prefer writing JS and applying your list.
Functional programming languages spoil you in this regard.
Weird!
> Unfortunately I don't think Clojurescript is worth the hassle(or even immutablejs or immer) and just prefer writing JS and applying your list.
Seems like we agree actually?
Famously Ramdajs on its front page has: we're not porting over all of the Clojure functions. To slow down this direction, for good reasons perhaps. Still its very visible that FP idioms travel nicely between langs/platforms.
I actually like many of the new parts - the class keyword being a notable exception. The other (much much milder) exception is async/await syntax - I love Promises but the async/await sugar on top of it this seems a little too much like magic. At the end of the day, they're still just Promises, just weirdly abstracted (just as classes are still just prototypes, just obscured so they look like something they're not).
The odd thing imo about the class keyword is it doesn't even seem to "fit" with the direction of may of the other new changes. E.g. arrow-functions are a very un-classy simplified functional form (no this re-assignment). This is very evident in the direction of many frameworks - e.g. React quickly moved to using classes (extends React.Component) and then quickly moved away from that convention (function components) as if realising their mistake.
Promises can get a little unwieldy raw - working on making that easier is a noble goal. But I'm never a fan of systems that actively obscure how something works. We now have plenty of people throwing asyncs and awaits around their code without a clue about how Promises work. It's significantly easier to learn how specific control flows work while writing raw Promises.
I never really liked any of the JS's attempts at OOP, be it the original prototype-based or the newer class-based, but I think that people were sufficiently confused enough by the prototype system that most people didn't bother with it. The class stuff is more approachable, and for some people that can sort of be viewed as a bad thing.
> true OOP languages
Specifically the Java used as a example isn't exactly a "true OOP language" either; in a sense it's actually a very leaky imperative VM with implementation details like non-object Primitive Data Types and very visible and prominent imperative flow control structures.
Javascript is much closer to an actual "true OOP language" as, unless you're dealing with bad/naive bindings to other stuff in C, it's very hard to even get a handle of a non-object.