It seems odd to read something written in 2017 about the confusion from using `this` which only mentions arrow functions briefly in passing. A fairly high percentage of discussion on that issue could be replaced with “use an arrow function, it works the way you expect”, and browser support should be basically the same as ES6 classes – doesn't work in IE11 but multiple tools are available for backwards compatibility.
Do arrow functions get added to the prototype or the instance of the object? Can they be created directly in the class body or have to be added inside the class constructor?
My understanding is that arrow functions aren’t used as part of a class definition, but for things like anonymous functions in callbacks, preserving the ‘this’ context when something else is invoking it.
E.g.
el.onClick((e) => {this.doSomething();});
In that case something else will invoke the onClick callback but we want the ‘this’ context to the same as when we set up the onClick.
Disadvantage is that creating a new function on every render ruins performance optimizations like `React.PureComponent`. Although that's a pretty minor detail, (you shouldn't prematurely optimize, and sometimes PureComponent is actually more expensive than rerendering since computing whether props have changed can be more expensive than just rerendering.)
That's why I mentioned it since it seems to be something like 90% of the bugs I've seen related to this. It's not a coincidence that this is the example used in the article.
Agreed - I don't find the usage of "this" to have any cognitive load, as long as arrow functions are used everywhere. "Arrow functions only" isn't a tough standard to maintain.
No it isn't. Its a function argument that is implicitly passed "left of the dot". Whatever object that function was attached to when you call it? Thats the object thats gonna become the `this` argument.
Normal arguments, you pass them like so: `someFunction(normalArgument1, normalArgument2)`
The argument `this` you pass it like so: `thisArgument.someFunction(normalArgument1, normalArgument2)`
From this it immediately becomes clear why you can't pass that function away by itself. When whoever got it calls it, its going to have nothing "left of the dot" to call it with.
Yeah, if you can't grasp 'this', good luck figuring out delegates. (Note: Some/many languages call these "Closures") 'this' is just a context pointer.
classes don't actually store their functions (except virtual ones).
class A{
int data;
void method1(int input){data = input;}
}
A my_instance;
is actually
struct A {int data;}
method1_for_struct_A(A *context, int input){ context->data = input; } //normal local function
A my_instance;
and this:
my_instance.method1(3);
becomes this:
method1_for_struct_A(my_instance, 3);
//send a pointer to the class we're operating on
And once you understand that, delegates are easy, because they're just... sending the context pointer around. So in D:
//note: global function, NOT apart of any class.
delegate void my_function(int input)
{
data = input; //where is data coming from?
// the surrounding context.
}
void my_test_function()
{
int data = 2;
my_function(3); // is a delegate and gets implicitly sent the context
//pointer for my_test_function, and now, it can access data.
// Note this is a RUN-TIME adjustable function. Delegates can be passed
// around.
}
and internally my_function is actually (note the pattern):
void my_function(context_pointer, int input)
{
context_pointer.data = input;
}
and the compiler implicitly sends the context with it:
So basically, delegates are just an extension of what classes already use under-the-hood. Delegates are "fat" function pointers. Function pointers point to a function, and the "fat" pointer includes an additional "context" pointer to the surrounding data frame so you can dereference it to access the surrounding data. You can do all of this in plain C if you don't mind writing all the boilerplate code yourself.
Virtual functions are similar to delegates. The class actually contains the function pointer / delegate instead of re-writing it at compile-time. Which allows them to be overridden at run-time. Whereas "this" usually means you can figure it out statically, at compile-time, so the function call is a pure function call and doesn't incur the penalty of looking up a pointer for the virtual call.
I bet I'm probably missing something or slightly off here or there, so someone feel free to chime in and correct me. Consider the code pseudo-code. I might have flipped something from memory.
In the global execution context (outside of any function), this refers to the global object whether in strict mode or not.
// In web browsers, the window object is also the global object:
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b) // "MDN"
console.log(b) // "MDN"
Inside a function, the value of this depends on how the function is called.
== Simple call ==
Since the following code is not in strict mode, and because the value of this is not set by the call, this will default to the global object, which is "window" in a browser.
function f1() {
return this;
}
// In a browser:
f1() === window; // true
// In Node:
f1() === global; // true
In strict mode, however, the value of this remains at whatever it was set to when entering the execution context, so, in the following case, this will default to undefined:
function f2() {
'use strict'; // see strict mode
return this;
}
f2() === undefined; // true
So, in strict mode, if this was not defined by the execution context, it remains undefined.
== The bind method ==
ECMAScript 5 introduced Function.prototype.bind. Calling f.bind(someObject) creates a new function with the same body and scope as f, but where this occurs in the original function, in the new function it is permanently bound to the first argument of bind, regardless of how the function is being used.
function f() {
return this.a;
}
var g = f.bind({a: 'azerty'});
console.log(g()); // azerty
var h = g.bind({a: 'yoo'}); // bind only works once!
console.log(h()); // azerty
== Arrow functions ==
In arrow functions, this retains the value of the enclosing lexical context's this. In global code, it will be set to the global object:
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
Note: if thisArg is passed to call, bind, or apply on invocation of an arrow function it will be ignored. You can still prepend arguments to the call, but the first argument (thisArg) should be set to null.
No matter what, foo's this is set to what it was when it was created. The same applies for arrow functions created inside other functions: their this remains that of the enclosing lexical context.
== As an object method ==
When a function is called as a method of an object, its this is set to the object the method is called on.
In the following example, when o.f() is invoked, inside the function this is bound to the o object.
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37
Note that this behavior is not at all affected by how or where the function was defined. In the previous example, we defined the function inline as the f member during the definition of o. However, we could have just as easily defined the function first and later attached it to o.f. Doing so results in the same behavior:
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // 37
This demonstrates that it matters only that the function ...
I'm a little amazed that you read all that, and still think it's so simple. Here's a simple counter argument to your "left of the dot" hypothesis:
setTimeout(foo.doStuff, 0);
When `doStuff` executes, `this` will not point to `foo`, even though it was "left of the dot". This same counter argument applies to every usage of a function as a first-class value.
Because I said left to the dot at "call time". More succintly:
> When you call a function, whatever is left of the dot is passed as the `this` argument
The analogy is pretty good - you can't pass an argument to a function until you actually call it. In your example, the function hasn't been called yet, so no argument has been passed.
foo.doStuff // <-- syntax to access a field member
foo.doStuff() // syntax to call a field member with a `this` argument passed left of the dot.
Furthermore, on the receiving side, the setTimeout function is seeing:
function setTimeout(fn, delay) {
// just fn, but not the argument.
}
Which makes it clear that you can no longer call that function with the proper `this` argument.
My point was that `this` is only as confusing as you make it, and there is a simple and straightforward way to teach it (function argument). If you do that, the dozens of seemingly special cases go down to just a couple.
If we had it, the explanation could be made even clearer. You would be able to call free-standing functions with a `this` argument left of the double colon, i.e. `thisArgument::freeStandingFunction()` - then the dot method call syntax `thisArgument.method()` could be explained as a syntax sugar for `thisArgument::(thisArgument.method)()` - a syntax sugar to at the same time access a function attached as a member field of an object and call it with that object as the `this` argument.
The bind operator would make all those other `this` cases seem less special too. Of course you can call various bits of code with whatever `this` argument you want. The event handler code? `someElement::eventHandlerCode(event)`. A constructor? `Object.create(constructor.prototype)::constructor()`. Nothing special here, just a function argument, can have different values in different functions, depending what was passed as `this`.
Its bizarre that this operator got stuck in stage 0 and is being subsumed by the pipeline operator (a terrible match for JavaScript). Looks like TC39 isn't interested in untangling the mess.
The thing is, I don't think anyone is really getting confused about `this` in your simplistic call-site left-of-the-dot model. They get confused when they do exactly the code I put, or similarly in a DOM event handler. And then there's weird cases, like calling bind on a bound function does nothing...
If we what to talk about what we wish it looked like, I wish it was more like Rust, where left-of-the-dot is an implicit argument to an explicit parameter within the function.
Once you partially apply the `this` argument you get a new function that calls the old function with the specified object as the `this` argument; that newly created function has its own implicit `this` argument, but it doesn't use it for anything, so it doesn't matter if you bind it.
Prototypical inheritance is also explained by the simplistic model. You call a method:
o.f()
The method is not on the object, so its looked up in its prototype chain. Lets say its found after the 2nd prototype:
[o.__proto__.f, o.__proto__.__proto__.f]
Once found its called, but left of the dot is still the original object `o` so that is what is going to become the `this` argument.
o::(o.__proto__.__proto__.f)()
To me this is simpler than any other late binding explanation I've read, for any language, ever.
So it's simplistic as long as you are capable of programming all these built ins with lower-level primitives... Which is not simple at all from the perspective of a beginner. I would think that once you get that far, `this` is probably not confusing anymore.
The confusing part for non-experts is that there are two "callable" types in JavaScript, functions and methods, but the language pretends that these two types are equivalent. Functions are self-contained and can be called without an object; methods depend on an implicit 'this' parameter. The fact that you can define and call a method using the syntax for a normal function call, without generating a type error, is a key part of the problem. The sequence "g = x.f; g()" should either be equivalent to "x.f()" or result in an error. Accepting both forms, but with subtly different behavior regarding implicit parameters, is bound to cause confusion.
Note that in C++ there is a type-level distinction between pointers to ordinary functions (without 'this') and pointers to member functions, so the concepts expressed above are not without precedent in "real" languages.
> To me this is simpler than any other late binding explanation I've read, for any language, ever.
The object system in Lua achieves the same effect with much less complexity, IMHO. Methods in Lua are just ordinary functions which take an object as an explicit first parameter; there is no implicit 'this' argument. Lua objects are tables (associative arrays), with metatables providing a fallback for missing keys much like JavaScript's prototype chain. The dot syntax (x.f) is nothing more than syntactic sugar for the table lookup x['f']. To call methods idiomatically you use the colon operator x:f(...), which is equivalent to x.f(x, ...) except that x is only evaluated once. Like JavaScript, Lua is dynamically typed, with missing arguments defaulting to nil, so you won't get a clean type error if you forget to pass the first parameter. However, there is no hidden, context-sensitive 'this' to worry about, and if you pass an argument fn=x.f to a function which calls it as fn(...) then the arguments will be exactly the same as if you had written x.f(...) directly—most likely not what you intended, but it is at least apparent why it failed.
No, there aren't two callable types in JavaScript. Thats precisely the point. Every single function has a `this` argument:
function f() { return this + 5; }
But there are only two ways to call it:
f.call(5) // calls with `this` argument set to 5
and
let x = new Number(5)
x.f = f;
x.f(); // syntax sugar for to x.f.call(x)
The confusing part here is that `x.f()` is very different from `x.f`, and is most definitely NOT the same as `(x.f)()`, but more of an `(x.f).call(x)` Other than that and the fact that the argument is always there even if you don't specify it, its exactly the same as Lua, metatables (prototypes) and all.
To resolve this confusion once and for all, we desperately need the bind operator [1] to provide a primitive on top of which the rest can be explained.
> No, there aren't two callable types in JavaScript. Thats precisely the point.
Yes, that _was_ precisely the point. Even though "methods" which require a context and "functions" which do not are logically two distinct types, JavaScript treats them as the same: 'this' is always present in some form. Then, since many (most?) functions _don't_ actually require a context, it allows "methods" to be called without an explicit value for 'this', which amounts to leaving out a parameter. Except that this particular parameter has a confusing default which depends on the context in which the call occurs. Contrast this with other languages where the equivalent of 'this' is either included in the function's argument list or available only to code with a special "method" type which must be called with an explicit context.
> The confusing part here is that `x.f()` is very different from `x.f`, and is most definitely NOT the same as `(x.f)()`, but more of an `(x.f).call(x)`
Yes, exactly. The behavior changes depending on whether "x.f" is treated as a method call ("x.f()") or a plain function call ("(x.f)()"). And yet the only visible difference is putting parentheses around a legal value expression, or assigning the value to a variable or function parameter before calling it. Contrast this with Lua, where the colon/method-call operator is only legal within a function call ("x:f" is a syntax error unless followed by an argument list) and there is no difference between "x.f()" and "(x.f)()".
> Other than [the difference between (x.f)() and x.f()] and the fact that the argument is always there even if you don't specify it, its exactly the same as Lua, metatables (prototypes) and all.
Agreed, though Lua also has a dedicated "method call" operator which might be considered significant. However, those differences are exactly why the Lua approach is less "magical" and easier to comprehend and work with. The underlying workings are bound to be similar since they are both Turing-complete dynamically-typed imperative languages with prototypical object systems.
Yeah well, it is what it is. Instead of having alternate syntax to pass the first argument, we can at least get first class syntax to pass a custom `this` argument. That will at least allow us to talk about `this` being a function argument, not a special magic "thing" - and there will be just one thing to learn, the big difference between (x.f)() and x.f()
> They get confused when they do exactly the code I put, or similarly in a DOM event handler.
Well, they're trying to operate on some thing, but they're not passing in that thing to operate on. (And usually while telling themselves that this jibes with FP, for some reason.) In fact, they're trying really hard to avoid explicitly passing in the thing they want to do something with. And they still want it to work the right way. Are the expectations reasonable?
That would muddy the water even further, since `with` normally brings object members into scope. This is specifically about calling a non-member that is already in scope, as if it were a member. Its exactly the same as `freeStandingFunction.call(thisArgument)`
It should be a primitive operator, not just a method on functions, because that allows you to explain x.method() without the explanation being self-referential i.e. fn.call(thisArgument) is a method call itself.
> This is specifically about calling a non-member that is already in scope, as if it were a member.
I understand what you wrote. `with` is reserved, but disused, however. And it wouldn't be the first reserved word that has different behavior based on the actual construct it appears in. Loops and switch statements both use `break`, for example. The language does this because the semantics are similar enough, and there's no point in polluting the list of reserved words when pulling double-duty with the one would do.
In the case of 'break', it breaks out of the block of the current construct (loop, switch). with brings an object in scope, or calls something already in scope with the object as a this argument - the semantics will be exactly opposite in this case.
Then it breaks chaining syntax (one nice thing we get), since everything will have to nest again:
x::fn()::otherFn() vs
(otherFn() with (fn() with x))
It brings more problems with ASI:
fn()
with (x); // is this fn() with (x), or fn(); with (x); do nothing
> No it isn't. Its a function argument that is implicitly passed "left of the dot".
That is true in JavaScript, but not in other languages that also have "this" and closures. So the confusion comes from two issues: 1. JavaScript is inconsistent with other languages, and 2. It's not at all clear that JavaScript's semantics are actually good.
In other languages where "this" is not loosely bound, there is next-to-no confusion around them and no problems, so the evidence is there that the semantics JavaScript chose aren't user-friendly.
The reason JS has those semantics is because it (at the time) had no way to distinguish between a method declaration (where this should be dynamically bound) and a function declaration (where this should be lexically bound). Other languages with class syntax don't have that problem.
JavaScript does have class syntax now, but changing the semantics of how this works would have been very difficult this many years later.
Funny that you mention "::" in Java. ES7/ES8 was going to have a bind operator "::" that extracts a bound method with a slightly different syntax. i.e. `::f.bar`
C++ also needs something like std::bind.
To have to use some bind mechanism is reasonable for me.
In JavaScript you could do this since quite some time with currying.
I thought of C++, but I have never written any JavaScript in my life. It's worth considering that even in C++, there are times when inventing a class to simply perform a function is excessive.
I don't have a sushi knife because I don't need one because I don't eat sushi.
I was in a project in rule based programming, but I left that project and f'got about such programming because I don't have any rules I want to program.
I was in a high end project for object-oriented programming and another for object-oriented data definitions (OSI/ISO CMIP/S) but f'got about those because I don't have any objects to program or use for data.
In my current project, I have defined a few classes, but it's not really object oriented programming because I do next to nothing with methods, never use inheritance, and usually allocate only one instance of each class.
So I use classes as really just versions of structures as in C and, much better than C, as in PL/I where the structures are terrific and in execution time much more efficient than objects. For fast program to program communications over TCP/IP sockets, I do like class instance de/serialization.
Basically, net, to me, object oriented programming is a solution for a niche problem I don't have, have never had, and likely never will have.
Also the OP illustrates more of why I don't pay attention to object-oriented programming: The syntax of that example is tricky gibberish I want nothing to do with.
Object oriented programming seems to have helped a lot with Microsoft's .NET. Okay, I use .NET. But I'm not writing .NET classes!
Sorry guys, I don't eat sushi and in my code don't see any real need for object-oriented programming.
More generally, I've been programming for a long time, and it all looks much the same to me: (A) Define places for the data need to store, (B) manipulate the data with assignment statements, if-then-else, do-while, call-return, and occasionally allocate storage(I let Microsoft's garbage collection handle freeing the storage), and (C) use some .NET classes.
I could use a sushi knife if I ate sushi, but I don't. I could use all that stuff about objects if I had objects in my programming, but mostly I don't.
To borrow from the old movie The Treasure of the Sierra Madre, "Objects? What objects? We don't need no stinkin' objects!"
Understanding 'this' in JavaScript is simply table stakes at this point. You and anyone else who touches your JS code should have a decent grasp on 'this'. Citing "complexity of this" while using React is just absurd in my opinion...
You Don't Know JS: this & Object Prototypes cleared it up nicely for me.[0] Love the series in general too.
Also -- and I'm not sure how it's typical done when taking a functional approach -- where do these functions "live"? What if we want to use a Person in another module/component/whatever? Do we basically just have a collection of "static" functions on a helper class that we're going to be using? I'm not sure that's a real gain. Would rather see the functions available on the Person itself (though maybe that's just my OOP ways talking).
And then there's the "pure function" part. Without explicit documentation and no other knowledge of this individual's functional predilections, I would be surprised when the setName function actually creates an entirely new Person and then sets it name.
I don't know. I really do enjoy functional approaches, but I don't think avoiding 'this' is a good reason, and I feel like the example given isn't the best scenario to illustrate.
I almost always want event listeners to have `this` refer to the element on which they were added, not the class from which they came. It's the default case for all but arrow functions, so it takes no extra effort in most cases, and if I really need other state, I find it is clearer if it comes through a closure. Plus, making it a habit makes it easier to keep it straight.
Nonsense. It's not a question of whether you understand `this` or not, it's a question of whether thinking about the different cases of `this` is really a good use of your time. Alternately: `this` is poorly designed and gets in your way.
In case the problem is non-obvious (that's the point!), the first line loses the binding to the specific `server` in question. Lovely. `this` in JS forces the consumer of a module (in this case `server`) to have knowledge of how the module represents its state in order to use it. That's not a good pattern.
> In case the problem is non-obvious (that's the point!),
I think what the gp was saying is that the table stakes for JS is this being obvious. I guess whether it is is subjective, but I have to agree. Your example is glaringly obvious to me. Any assignment of a method on an object, or passing of one as an argument (i.e. any passing/assignment that includes a dot before a method) stands out. This doesn't involve any cognitive overhead if you actually know what `this` is.
> `this` in JS forces the consumer of a module (in this case `server`) to have knowledge of how the module represents its state in order to use it.
No it doesn't... It requires the method to have access to the module it's defined on, which means you can't pass (i.e. reassign) the method on its own. In your example, the module (server) is not being consumed, only one of the modules methods is consumed on its own; the module is left behind and not consumed (i.e. `this` is left behind)
It's kind of funny to hear that the example is 'glaringly obvious', given that if the module in question was implemented as a singleton, the first line would be both correct and preferable...
The fact that your statement includes a qualifier ("if the module... was implemented as...") means it's never going to be preferable. Nothing should depend on a qualifier that isn't self-evident from syntax.
Abstracting `server.close()` and naming it `closeServer` may seem to be a bit redundant, but the example given was slightly contrived so this would be almost always preferable in practice.
The more I learn JS the more I am worried that I am not using 'this' right.
I swap back and forth between javascript/python and with JS it always feels like I am one step away from disaster if I accidentally forget to bind this.
The Gambit he's claiming is straightforward. Given type X, calling object of type X, a's method a.doit() is just sugar on the fundamental doit(a). The way you get the former is to introduce JS concept of _this_ which is accidental complexity that doesn't pull its weight. I tend to agree.
Why Can't JavaScript just have c++/java/c# type classes? At least it makes it easy to identify what you class variables are and what your class functions are.
What bugs me about the class syntax more than anything is that it encourages prototype classes, which in my humble opinion, are just worse than the Crockford module pattern. C Modules solve this/binding issues, and give you privacy. Maybe they use more memory (in theory), but I never thought that was a great argument unless you actually measure usage and it's noticeably worse. You're basically creating those functions anyway when you call bind, so I wouldn't be surprised if there's not a big effect for most applications.
I also find `this` is terrible for linting. Most tools won't be able to figure you what you attached to `this` at runtime. It's a grab bag object ripe for bugs.
If you're going to go with a functional approach (which he introduces in the section "How to avoid 'this'"), instead of creating a brand-new function for each particular piece of data (e.g., setName, setGreeting) -- which will result in a ton of extra code -- just use a simple, generic assoc function.
"Inventing a class with its own interface to hold a piece of information is like inventing a new language to write every short story." -- Rich Hickey
I actually have to maintain a js codebase that uses a generic “addProperty” function at its core to generate getters and setters. It is terrible to debug. Properly encapsulated class methods are much easier to read and debug.
This problem has been solved for a long time. Some data doesn’t need a class, but when it does, don’t roll your own.
This post is painfully confusing the important concepts of complexity & "complecting"/coupling.
Javascript's this semantics may be confusing and hard to reason about but that is a different charge than the complecting/coupling sort of complexity which is the _much, much_ bigger problem in the world of building software systems.
The much bigger problem with a Person class is not to do with this or that programming language vagaries vis a vis the this keyword. The bigger problem exists in all programming languages: ADTs like Person classes introduce unnecessary coupling of information -- and this is what really matters: this is what hurts productivity.
It sucks that I have to learn the finicky vagaries of this in Javascript, but once I do, I'm fairly good going forward. However, no amount of learning on my side is going to help me when I need to add new pieces of information to some people data -- your ADT has decided for and _requires_ me to jam my knowledge and facts into an unnecessary rigid categorization in order to evolve your ADT-ridden data processing system.
This post's refactoring of class hasn't changed that game for me. You've just moved this to each function's first parameter and called it "person" -- so while you've saved me momentarily from having to learn about this semantics[1], you haven't gotten rid of the truly offensive bit: the whole notion of the "person" classification itself and the rigid, upfront, unnecessary, and likely-to-be-wrong-in-the-future assumption that only its members can be given these things called names.
[1] Which I should have learnt anyway being a Javascript programmer.
What do you mean by ADTs introduce unnecessary coupling of information? ADTs allow you to clarify and safely handle otherwise vague data. Maybe I'm misunderstanding, but your comment reads to me like you're in support of stringly typed programming.
> ADTs allow you to clarify and safely handle otherwise vague data.
This is false. You can program with clear, strong, safe semantics without needing to taxonomize/classify your business domain/entities with ADTs.
In most business applications, ADTs hurt you: they are used to pre-classify the world in ways (unnecessary ways) that very frequently collide with future system evolution & future use-cases.
That doesn't seem to have anything to do with ADTs but rather all code in general.
I'm not sure how "// request.user will be undefined or { id, uname }" is any more future durable or omniscient than `Request.User : Guest | LoggedIn User`.
The latter just encodes the possible states directly and explicitly.
Your line of reasoning is like saying that Mongo lets you adapt to future requirements better than Postgres because you don't have a schema.
> The latter just encodes the possible states directly and explicitly.
The very concept of "possible states" is the problem. By deciding that your domain entity has possible valid states in all situations (which is more or less what an ADT is saying), therein is the coupling, the pain, the productivity impediment. Not only do you not need to say that for all moments and places in my program a person must have a { id, name, uname, age, etc, etc }, it hurts you to assert that because the future is always much more creative than you and will soon put demands that exceed the assertion.
> Your line of reasoning is like saying that Mongo lets you adapt to future requirements better than Postgres because you don't have a schema.
Yes, that's precisely correct. As anyone who has had to collaborate across multiple features and/or teams can attest. In a NoSQL like system, I can bring in a new fact/information without flinching. In Postgres, I have to negotiate with the schema system and all of the consumers of the data, which obviously isn't necessary b/c NoSQL. (What Postgres may buy you is some transactional semantics and other relational db goodies etc but it is certainly not buying you adaptability/agility over the NoSQL/NoSchema store.)
Can you clarify your argument/provide a possible solution?
Your line of thinking seems kind of absurd to me - "Hey, we can't possibly predict our data model requirements in the future, and explicitly encoding them into a schema/ADT/etc hinders flexibility (and somehow productivity?)" doesn't really hold up to scrutiny. Flexibility is great, but if anything the lesson mongo taught us over the past few years is that stability and safety are more important, and will cause you less of a headache in the long term. I feel like the same applies to ADTs - of course its more flexible to just have a free floating `name: String` or whatever, but then you also get to have the fun of validating your data all day as opposed to letting your compiler do the dirty work for you.
> Flexibility is great, but if anything the lesson mongo taught us over the past few years is that stability and safety are more important, and will cause you less of a headache in the long term.
It sounds like you don't really need clarification, that you've learned the same lessons I have, but that you're just prioritizing differently and explicitly choosing "stability and safety" over flexibility (adaptability/agility).
I go the other way. The biggest software costs in my experience are wanting to pivot on this or that business idea/requirement but being severely hindered[1] due to some unnecessary data taxonomization decisions that are so woven into the code (via ADTs/entity types) presumably for "safety" or "stability".
By locking things down with ADTs and type taxonomies you most certainly get stability, I won't argue with that. But that stability by definition is rigidity. I want things to be dynamic. I want to be able to readily change my code assets because in most business operations the future demands that.
Do you get "safety" with this stability as well? Probably, perhaps - but this would warrant a whole conversation about what is safety.
In a dynamic/agile business environment, I personally will give up a little bit of safety for agility and dynamism. Because having an easily-fixable bug show up in production is far less costly to me than being paralyzed by an increasingly rigid/"stable" system that is hard to modify for future purposes. And even if I'm truly worried about bug volume, I can decouple that concern with the code agility concern. That is, I don't have to weave my bug mitigation concern into my codebase via hard type taxonomies. I can use other measures like code reviews, QA & testing, etc. and turn the knob up or down on these as a I see fit.
The systems that weave all these concerns together in my experience always become harder and harder to bring new capability into. This is and should be no surprise, though. The world does not fit neatly into static, hard-line taxonomies. Our OCD minds would love it if it they did and people really try to make it fit into that (hence all the ADT hoopla we see everywhere) -- but in my experience the world always finds creative ways to frustrate our taxonomies...and systems that predicate themselves on a taxonomy always get frustrated by that.
[1] Severely hindered means that something that should take very little time with very little impact on your app suite and very little coordination across the codebase(s) and team(s) instead requires all of these things.
If we can all agree about that then we have to ask ourselves if encapsulating data and behavior at the granularity of classes makes the most sense for human comprehension. In naive OO examples (Animal -> Cat) it does. In practice however, it's debatable (at best).
An inheritance hierarchy consists of three things: a taxonomy, behavioural inheritance and structural inheritance. The first is only useful on occasion, and the last two don't even apply to Animal → Cat; "animal" is not a behavioural constraint, and a cat certainly doesn't have a platonic ideal of an animal hidden somewhere inside its gut.
I avoid classes as a default behaviour, using them when they're clearly a good fit. The ease of testability that functions give me is worth its weight in gold.
I love how much I can do with JavaScript without classes. Same with Python! You'd be surprised how often you don't need them.
A lot of the typical use cases for classes in a language like Java are replaced by the user's choice of module format in JavaScript (ES6, commonjs, etc.). Though, I don't think "this" was that hard to learn (See: YDKJS) and classes/prototypes definitely are an important tool in the JavaScript toolbelt.
This unfortunately is one of those cases where the fact that someone has taken some time to write a nice looking article about a subject does not signal that they have something interesting and valuable to say.
What about the cognitive load on someone reading person1 person2 person3. Now I have to realize person 3 in your code is person2 and person2 was person1, person1 and person2 are now dangling and will never be touched again hopefully. I love the idea of pure functions but I have always thought there was a missing piece in functional programming.
No sane language makes you write person1 person2 person3. Immutability doesn't mean you can't reuse names; only that the reassignment is in lexical scope, and doesn't impact any other references to what you saw as person1.
Rust calls it shadowing and throws this example:
fn main() {
let x = 5;
let x = x + 1;
let x = x * 2;
println!("The value of x is: {}", x);
}
Sure this is a syntax example and you shouldn't be creating Immutable variable x and then adding +1 to it for no reason. If you did have a reason you should add it to the variable name so it's more likely developers say let xPlusOne = x + 1;
Or a kitchen example:
Let cream = getRefridgeratorItem(cream);
Let whippedCream = blend(cream);
So you reply no sane language makes you do this but why didn't the author show that? His goal was to make things more readable but lets be honest functional programmers would judge you all day for making a promise and then copying over that promise with something else. You may do it at work but the purist trolls hate reading it.
A few years back I wrote an JS+async to JS compiler. I made a very real attempt to forget everything I knew about statically typed languages.
I organically found a very compositional form. The parser created a tree containing objects such as, for an if statement, { if: { ... }, accept: () => ... }. The next stage would decompose that into more compositions, maybe by adding a block and condition property. While it probably had garbage performance, the power of dynamic languages finally clicked.
I used to be an aspiring game developer; writing a component entity system in C# (my weapon of choice) required absurd amounts of carefully crafted code. Statically-typed languages have a ton of front-loaded overhead if you need something outside of their formula.
You can approach problems any way that you please in dynamic languages (such as the functional approach in the article) and, while they still really aren't for me, I completely understand why people use them.
It's not clear to me what you mean by the power of dynamic languages. I'm inclined to think your perspective on static languages is limited to C# or some other OOP static languages. In particular, your example doesn't seem very novel for a statically typed language. Heck, I just built a reasonably elegant parser framework in Go and it doesn't even have generics. This makes me wonder if you're not attributing your OOP frustrations to static typing.
I really did mean to say OOP - I have been attempting a real project in Go and I'm smitten.
Still, even Go has a prescribed way of approaching problems; as unusually broad as it is for a statically typed language. As a very relevant example, there is far more syntactical overhead in Go if you wish to fake FP (e.g. `func Foo(a A) func(b) func(c) func(d) e` to allow currying); where-as in JavaScript you simply bind or apply.
I've yet to see an article gushing over how natural functional is in Go (which is fine, Go isn't designed for that). This must be the 20th article I've seen on HN about functional in JavaScript.
Yeah, I agree with your assessments of Go. I didn't mean for it to sound like Go is a good example of expressive static typing--quite the opposite: I was able to build something elegant with Go in spite of its limited, static type system.
Too bad avoiding this just looks like a spaghetti mess with no cohesion. If you're going to be using a language, you might as well learn how it works instead of avoiding it with strange hacks. I'm also confused how the 'pure' version is less complicated than the class version. I see it the opposite?
"
Usage of class is not bad. It definitely has its place. If you have some really “hot” code that’s a bottleneck for your application, then using class can really speed things up. But 99% of the time, that’s not the case. And I don’t see how classes and the added complexity of this is worth it for most cases (let’s not even get started with prototypal inheritance). I have yet to have a situation where I needed classes for performance. So I only use them for React components because that’s what you have to do if you need to use state/lifecycle methods (but maybe not in the future).
"
Why would classes have any inherent performance benefits (and over what) ?
Since classes are just sugar over prototypes, their performance is just about the same for instantiation. I assume the article is comparing them to functions that return an object literal with methods.
Here is a simple example of equivalent code in a class, function with a closure, and a prototype:
When instantiating a lot of objects, the prototype and class are practically the same, but the function closure pattern is a lot slower. On my machine, instantiating one hundred million of the class or prototype each took about 500ms, but the closure function took about 3s.
I don't see why would I want to assign my bound class methods to external variable, and if you do that, you are responsible to bind the method to an instance. The reason it is a class method is because it operates on that class, taking it out and using it externally is against the purpose of it's existence.
97 comments
[ 2.7 ms ] story [ 191 ms ] threadSo, I'd say that this book is still relevant Today. If you don't understand how inheritance work behind the scenes, you don't understand JavaScript.
https://github.com/jeffmo/es-class-fields-and-static-propert...
E.g. el.onClick((e) => {this.doSomething();});
In that case something else will invoke the onClick callback but we want the ‘this’ context to the same as when we set up the onClick.
No it isn't. Its a function argument that is implicitly passed "left of the dot". Whatever object that function was attached to when you call it? Thats the object thats gonna become the `this` argument.
Normal arguments, you pass them like so: `someFunction(normalArgument1, normalArgument2)`
The argument `this` you pass it like so: `thisArgument.someFunction(normalArgument1, normalArgument2)`
From this it immediately becomes clear why you can't pass that function away by itself. When whoever got it calls it, its going to have nothing "left of the dot" to call it with.
Done, `this` explained.
classes don't actually store their functions (except virtual ones).
is actually and this: becomes this: And once you understand that, delegates are easy, because they're just... sending the context pointer around. So in D: and internally my_function is actually (note the pattern): and the compiler implicitly sends the context with it: So basically, delegates are just an extension of what classes already use under-the-hood. Delegates are "fat" function pointers. Function pointers point to a function, and the "fat" pointer includes an additional "context" pointer to the surrounding data frame so you can dereference it to access the surrounding data. You can do all of this in plain C if you don't mind writing all the boilerplate code yourself.Virtual functions are similar to delegates. The class actually contains the function pointer / delegate instead of re-writing it at compile-time. Which allows them to be overridden at run-time. Whereas "this" usually means you can figure it out statically, at compile-time, so the function call is a pure function call and doesn't incur the penalty of looking up a pointer for the virtual call.
I bet I'm probably missing something or slightly off here or there, so someone feel free to chime in and correct me. Consider the code pseudo-code. I might have flipped something from memory.
In the global execution context (outside of any function), this refers to the global object whether in strict mode or not.
Inside a function, the value of this depends on how the function is called.== Simple call ==
Since the following code is not in strict mode, and because the value of this is not set by the call, this will default to the global object, which is "window" in a browser.
In strict mode, however, the value of this remains at whatever it was set to when entering the execution context, so, in the following case, this will default to undefined: So, in strict mode, if this was not defined by the execution context, it remains undefined.== The bind method ==
ECMAScript 5 introduced Function.prototype.bind. Calling f.bind(someObject) creates a new function with the same body and scope as f, but where this occurs in the original function, in the new function it is permanently bound to the first argument of bind, regardless of how the function is being used.
== Arrow functions ==In arrow functions, this retains the value of the enclosing lexical context's this. In global code, it will be set to the global object:
Note: if thisArg is passed to call, bind, or apply on invocation of an arrow function it will be ignored. You can still prepend arguments to the call, but the first argument (thisArg) should be set to null.No matter what, foo's this is set to what it was when it was created. The same applies for arrow functions created inside other functions: their this remains that of the enclosing lexical context.
== As an object method ==
When a function is called as a method of an object, its this is set to the object the method is called on.
In the following example, when o.f() is invoked, inside the function this is bound to the o object.
Note that this behavior is not at all affected by how or where the function was defined. In the previous example, we defined the function inline as the f member during the definition of o. However, we could have just as easily defined the function first and later attached it to o.f. Doing so results in the same behavior: This demonstrates that it matters only that the function ...Everything else, still explained by ”argument passed left of the dot at call time“
Oh yeah and arrow functions dont have a this argument so whatever this was already in scope is the binding.
> When you call a function, whatever is left of the dot is passed as the `this` argument
The analogy is pretty good - you can't pass an argument to a function until you actually call it. In your example, the function hasn't been called yet, so no argument has been passed.
Furthermore, on the receiving side, the setTimeout function is seeing: Which makes it clear that you can no longer call that function with the proper `this` argument.My point was that `this` is only as confusing as you make it, and there is a simple and straightforward way to teach it (function argument). If you do that, the dozens of seemingly special cases go down to just a couple.
Its unfortunate we don't yet have the bind operator https://github.com/tc39/proposal-bind-operator
If we had it, the explanation could be made even clearer. You would be able to call free-standing functions with a `this` argument left of the double colon, i.e. `thisArgument::freeStandingFunction()` - then the dot method call syntax `thisArgument.method()` could be explained as a syntax sugar for `thisArgument::(thisArgument.method)()` - a syntax sugar to at the same time access a function attached as a member field of an object and call it with that object as the `this` argument.
The bind operator would make all those other `this` cases seem less special too. Of course you can call various bits of code with whatever `this` argument you want. The event handler code? `someElement::eventHandlerCode(event)`. A constructor? `Object.create(constructor.prototype)::constructor()`. Nothing special here, just a function argument, can have different values in different functions, depending what was passed as `this`.
Its bizarre that this operator got stuck in stage 0 and is being subsumed by the pipeline operator (a terrible match for JavaScript). Looks like TC39 isn't interested in untangling the mess.
If we what to talk about what we wish it looked like, I wish it was more like Rust, where left-of-the-dot is an implicit argument to an explicit parameter within the function.
Calling bind on a bound function is also explained by the simplistic model:
Once you partially apply the `this` argument you get a new function that calls the old function with the specified object as the `this` argument; that newly created function has its own implicit `this` argument, but it doesn't use it for anything, so it doesn't matter if you bind it.Prototypical inheritance is also explained by the simplistic model. You call a method:
The method is not on the object, so its looked up in its prototype chain. Lets say its found after the 2nd prototype: Once found its called, but left of the dot is still the original object `o` so that is what is going to become the `this` argument. To me this is simpler than any other late binding explanation I've read, for any language, ever.Note that in C++ there is a type-level distinction between pointers to ordinary functions (without 'this') and pointers to member functions, so the concepts expressed above are not without precedent in "real" languages.
> To me this is simpler than any other late binding explanation I've read, for any language, ever.
The object system in Lua achieves the same effect with much less complexity, IMHO. Methods in Lua are just ordinary functions which take an object as an explicit first parameter; there is no implicit 'this' argument. Lua objects are tables (associative arrays), with metatables providing a fallback for missing keys much like JavaScript's prototype chain. The dot syntax (x.f) is nothing more than syntactic sugar for the table lookup x['f']. To call methods idiomatically you use the colon operator x:f(...), which is equivalent to x.f(x, ...) except that x is only evaluated once. Like JavaScript, Lua is dynamically typed, with missing arguments defaulting to nil, so you won't get a clean type error if you forget to pass the first parameter. However, there is no hidden, context-sensitive 'this' to worry about, and if you pass an argument fn=x.f to a function which calls it as fn(...) then the arguments will be exactly the same as if you had written x.f(...) directly—most likely not what you intended, but it is at least apparent why it failed.
To resolve this confusion once and for all, we desperately need the bind operator [1] to provide a primitive on top of which the rest can be explained.
[1]: https://github.com/tc39/proposal-bind-operator
Yes, that _was_ precisely the point. Even though "methods" which require a context and "functions" which do not are logically two distinct types, JavaScript treats them as the same: 'this' is always present in some form. Then, since many (most?) functions _don't_ actually require a context, it allows "methods" to be called without an explicit value for 'this', which amounts to leaving out a parameter. Except that this particular parameter has a confusing default which depends on the context in which the call occurs. Contrast this with other languages where the equivalent of 'this' is either included in the function's argument list or available only to code with a special "method" type which must be called with an explicit context.
> The confusing part here is that `x.f()` is very different from `x.f`, and is most definitely NOT the same as `(x.f)()`, but more of an `(x.f).call(x)`
Yes, exactly. The behavior changes depending on whether "x.f" is treated as a method call ("x.f()") or a plain function call ("(x.f)()"). And yet the only visible difference is putting parentheses around a legal value expression, or assigning the value to a variable or function parameter before calling it. Contrast this with Lua, where the colon/method-call operator is only legal within a function call ("x:f" is a syntax error unless followed by an argument list) and there is no difference between "x.f()" and "(x.f)()".
> Other than [the difference between (x.f)() and x.f()] and the fact that the argument is always there even if you don't specify it, its exactly the same as Lua, metatables (prototypes) and all.
Agreed, though Lua also has a dedicated "method call" operator which might be considered significant. However, those differences are exactly why the Lua approach is less "magical" and easier to comprehend and work with. The underlying workings are bound to be similar since they are both Turing-complete dynamically-typed imperative languages with prototypical object systems.
Well, they're trying to operate on some thing, but they're not passing in that thing to operate on. (And usually while telling themselves that this jibes with FP, for some reason.) In fact, they're trying really hard to avoid explicitly passing in the thing they want to do something with. And they still want it to work the right way. Are the expectations reasonable?
If you're going to do that, you might as well do it the right way and make it `verb::theThing` instead.
Alternatively, `with` is an existing reserved word that already kind of deals in this realm, as language semantics go. So...
It should be a primitive operator, not just a method on functions, because that allows you to explain x.method() without the explanation being self-referential i.e. fn.call(thisArgument) is a method call itself.
I understand what you wrote. `with` is reserved, but disused, however. And it wouldn't be the first reserved word that has different behavior based on the actual construct it appears in. Loops and switch statements both use `break`, for example. The language does this because the semantics are similar enough, and there's no point in polluting the list of reserved words when pulling double-duty with the one would do.
Then it breaks chaining syntax (one nice thing we get), since everything will have to nest again:
It brings more problems with ASI:That is true in JavaScript, but not in other languages that also have "this" and closures. So the confusion comes from two issues: 1. JavaScript is inconsistent with other languages, and 2. It's not at all clear that JavaScript's semantics are actually good.
In other languages where "this" is not loosely bound, there is next-to-no confusion around them and no problems, so the evidence is there that the semantics JavaScript chose aren't user-friendly.
The reason JS has those semantics is because it (at the time) had no way to distinguish between a method declaration (where this should be dynamically bound) and a function declaration (where this should be lexically bound). Other languages with class syntax don't have that problem.
JavaScript does have class syntax now, but changing the semantics of how this works would have been very difficult this many years later.
Python has an explicit self that is unbound. Other languages have it implicit and bound... Etc
Funny that you mention "::" in Java. ES7/ES8 was going to have a bind operator "::" that extracts a bound method with a slightly different syntax. i.e. `::f.bar`
Weird.
I was in a project in rule based programming, but I left that project and f'got about such programming because I don't have any rules I want to program.
I was in a high end project for object-oriented programming and another for object-oriented data definitions (OSI/ISO CMIP/S) but f'got about those because I don't have any objects to program or use for data.
In my current project, I have defined a few classes, but it's not really object oriented programming because I do next to nothing with methods, never use inheritance, and usually allocate only one instance of each class.
So I use classes as really just versions of structures as in C and, much better than C, as in PL/I where the structures are terrific and in execution time much more efficient than objects. For fast program to program communications over TCP/IP sockets, I do like class instance de/serialization.
Basically, net, to me, object oriented programming is a solution for a niche problem I don't have, have never had, and likely never will have.
Also the OP illustrates more of why I don't pay attention to object-oriented programming: The syntax of that example is tricky gibberish I want nothing to do with.
Object oriented programming seems to have helped a lot with Microsoft's .NET. Okay, I use .NET. But I'm not writing .NET classes!
Sorry guys, I don't eat sushi and in my code don't see any real need for object-oriented programming.
More generally, I've been programming for a long time, and it all looks much the same to me: (A) Define places for the data need to store, (B) manipulate the data with assignment statements, if-then-else, do-while, call-return, and occasionally allocate storage(I let Microsoft's garbage collection handle freeing the storage), and (C) use some .NET classes.
I could use a sushi knife if I ate sushi, but I don't. I could use all that stuff about objects if I had objects in my programming, but mostly I don't.
To borrow from the old movie The Treasure of the Sierra Madre, "Objects? What objects? We don't need no stinkin' objects!"
Using arrow functions at the class level would also solve this without adding new syntax. Edit, this is a thing already: https://babeljs.io/docs/plugins/transform-class-properties/
foo.bar.bind(somethingElse);
How would the callee know that this would potentially conflict?
does this confusion not exist now?
See https://github.com/tc39/proposal-class-fields.
You Don't Know JS: this & Object Prototypes cleared it up nicely for me.[0] Love the series in general too.
Also -- and I'm not sure how it's typical done when taking a functional approach -- where do these functions "live"? What if we want to use a Person in another module/component/whatever? Do we basically just have a collection of "static" functions on a helper class that we're going to be using? I'm not sure that's a real gain. Would rather see the functions available on the Person itself (though maybe that's just my OOP ways talking).
And then there's the "pure function" part. Without explicit documentation and no other knowledge of this individual's functional predilections, I would be surprised when the setName function actually creates an entirely new Person and then sets it name.
I don't know. I really do enjoy functional approaches, but I don't think avoiding 'this' is a good reason, and I feel like the example given isn't the best scenario to illustrate.
[0] https://github.com/getify/You-Dont-Know-JS/tree/master/this%...
Here's an example of a recent production bug:
And one way to fix: In case the problem is non-obvious (that's the point!), the first line loses the binding to the specific `server` in question. Lovely. `this` in JS forces the consumer of a module (in this case `server`) to have knowledge of how the module represents its state in order to use it. That's not a good pattern.I think what the gp was saying is that the table stakes for JS is this being obvious. I guess whether it is is subjective, but I have to agree. Your example is glaringly obvious to me. Any assignment of a method on an object, or passing of one as an argument (i.e. any passing/assignment that includes a dot before a method) stands out. This doesn't involve any cognitive overhead if you actually know what `this` is.
> `this` in JS forces the consumer of a module (in this case `server`) to have knowledge of how the module represents its state in order to use it.
No it doesn't... It requires the method to have access to the module it's defined on, which means you can't pass (i.e. reassign) the method on its own. In your example, the module (server) is not being consumed, only one of the modules methods is consumed on its own; the module is left behind and not consumed (i.e. `this` is left behind)
As for what is preferable...
Abstracting `server.close()` and naming it `closeServer` may seem to be a bit redundant, but the example given was slightly contrived so this would be almost always preferable in practice.I swap back and forth between javascript/python and with JS it always feels like I am one step away from disaster if I accidentally forget to bind this.
I've been preferring just setting up the bindings in the constructor over arrow functions.
So for the author's example I'd just do: constructor (){ this.increment.bind(this); }
Then the template can stay cleaner with just onClick={this.increment}
I've been doing it for a while and haven't had any problems and have really enjoyed doing it.
"Inventing a class with its own interface to hold a piece of information is like inventing a new language to write every short story." -- Rich Hickey
This problem has been solved for a long time. Some data doesn’t need a class, but when it does, don’t roll your own.
Inheritance, overrides, super calls? The less I use those, the less I miss them.
Javascript's this semantics may be confusing and hard to reason about but that is a different charge than the complecting/coupling sort of complexity which is the _much, much_ bigger problem in the world of building software systems.
The much bigger problem with a Person class is not to do with this or that programming language vagaries vis a vis the this keyword. The bigger problem exists in all programming languages: ADTs like Person classes introduce unnecessary coupling of information -- and this is what really matters: this is what hurts productivity.
It sucks that I have to learn the finicky vagaries of this in Javascript, but once I do, I'm fairly good going forward. However, no amount of learning on my side is going to help me when I need to add new pieces of information to some people data -- your ADT has decided for and _requires_ me to jam my knowledge and facts into an unnecessary rigid categorization in order to evolve your ADT-ridden data processing system.
This post's refactoring of class hasn't changed that game for me. You've just moved this to each function's first parameter and called it "person" -- so while you've saved me momentarily from having to learn about this semantics[1], you haven't gotten rid of the truly offensive bit: the whole notion of the "person" classification itself and the rigid, upfront, unnecessary, and likely-to-be-wrong-in-the-future assumption that only its members can be given these things called names.
[1] Which I should have learnt anyway being a Javascript programmer.
This is false. You can program with clear, strong, safe semantics without needing to taxonomize/classify your business domain/entities with ADTs.
In most business applications, ADTs hurt you: they are used to pre-classify the world in ways (unnecessary ways) that very frequently collide with future system evolution & future use-cases.
I'm not sure how "// request.user will be undefined or { id, uname }" is any more future durable or omniscient than `Request.User : Guest | LoggedIn User`.
The latter just encodes the possible states directly and explicitly.
Your line of reasoning is like saying that Mongo lets you adapt to future requirements better than Postgres because you don't have a schema.
The very concept of "possible states" is the problem. By deciding that your domain entity has possible valid states in all situations (which is more or less what an ADT is saying), therein is the coupling, the pain, the productivity impediment. Not only do you not need to say that for all moments and places in my program a person must have a { id, name, uname, age, etc, etc }, it hurts you to assert that because the future is always much more creative than you and will soon put demands that exceed the assertion.
> Your line of reasoning is like saying that Mongo lets you adapt to future requirements better than Postgres because you don't have a schema.
Yes, that's precisely correct. As anyone who has had to collaborate across multiple features and/or teams can attest. In a NoSQL like system, I can bring in a new fact/information without flinching. In Postgres, I have to negotiate with the schema system and all of the consumers of the data, which obviously isn't necessary b/c NoSQL. (What Postgres may buy you is some transactional semantics and other relational db goodies etc but it is certainly not buying you adaptability/agility over the NoSQL/NoSchema store.)
Your line of thinking seems kind of absurd to me - "Hey, we can't possibly predict our data model requirements in the future, and explicitly encoding them into a schema/ADT/etc hinders flexibility (and somehow productivity?)" doesn't really hold up to scrutiny. Flexibility is great, but if anything the lesson mongo taught us over the past few years is that stability and safety are more important, and will cause you less of a headache in the long term. I feel like the same applies to ADTs - of course its more flexible to just have a free floating `name: String` or whatever, but then you also get to have the fun of validating your data all day as opposed to letting your compiler do the dirty work for you.
It sounds like you don't really need clarification, that you've learned the same lessons I have, but that you're just prioritizing differently and explicitly choosing "stability and safety" over flexibility (adaptability/agility).
I go the other way. The biggest software costs in my experience are wanting to pivot on this or that business idea/requirement but being severely hindered[1] due to some unnecessary data taxonomization decisions that are so woven into the code (via ADTs/entity types) presumably for "safety" or "stability".
By locking things down with ADTs and type taxonomies you most certainly get stability, I won't argue with that. But that stability by definition is rigidity. I want things to be dynamic. I want to be able to readily change my code assets because in most business operations the future demands that.
Do you get "safety" with this stability as well? Probably, perhaps - but this would warrant a whole conversation about what is safety.
In a dynamic/agile business environment, I personally will give up a little bit of safety for agility and dynamism. Because having an easily-fixable bug show up in production is far less costly to me than being paralyzed by an increasingly rigid/"stable" system that is hard to modify for future purposes. And even if I'm truly worried about bug volume, I can decouple that concern with the code agility concern. That is, I don't have to weave my bug mitigation concern into my codebase via hard type taxonomies. I can use other measures like code reviews, QA & testing, etc. and turn the knob up or down on these as a I see fit.
The systems that weave all these concerns together in my experience always become harder and harder to bring new capability into. This is and should be no surprise, though. The world does not fit neatly into static, hard-line taxonomies. Our OCD minds would love it if it they did and people really try to make it fit into that (hence all the ADT hoopla we see everywhere) -- but in my experience the world always finds creative ways to frustrate our taxonomies...and systems that predicate themselves on a taxonomy always get frustrated by that.
[1] Severely hindered means that something that should take very little time with very little impact on your app suite and very little coordination across the codebase(s) and team(s) instead requires all of these things.
If we can all agree about that then we have to ask ourselves if encapsulating data and behavior at the granularity of classes makes the most sense for human comprehension. In naive OO examples (Animal -> Cat) it does. In practice however, it's debatable (at best).
I highly recommend this video by Brian Will who does a much better job explaining this problem than I could. https://www.youtube.com/watch?v=QM1iUe6IofM
Douglas Crockford also does a good job of explaining some of the trouble with classes. https://www.youtube.com/watch?v=bo36MrBfTk4#t=28m50s
An inheritance hierarchy consists of three things: a taxonomy, behavioural inheritance and structural inheritance. The first is only useful on occasion, and the last two don't even apply to Animal → Cat; "animal" is not a behavioural constraint, and a cat certainly doesn't have a platonic ideal of an animal hidden somewhere inside its gut.
I love how much I can do with JavaScript without classes. Same with Python! You'd be surprised how often you don't need them.
Sure this is a syntax example and you shouldn't be creating Immutable variable x and then adding +1 to it for no reason. If you did have a reason you should add it to the variable name so it's more likely developers say let xPlusOne = x + 1; Or a kitchen example: Let cream = getRefridgeratorItem(cream); Let whippedCream = blend(cream);
So you reply no sane language makes you do this but why didn't the author show that? His goal was to make things more readable but lets be honest functional programmers would judge you all day for making a promise and then copying over that promise with something else. You may do it at work but the purist trolls hate reading it.
I organically found a very compositional form. The parser created a tree containing objects such as, for an if statement, { if: { ... }, accept: () => ... }. The next stage would decompose that into more compositions, maybe by adding a block and condition property. While it probably had garbage performance, the power of dynamic languages finally clicked.
I used to be an aspiring game developer; writing a component entity system in C# (my weapon of choice) required absurd amounts of carefully crafted code. Statically-typed languages have a ton of front-loaded overhead if you need something outside of their formula.
You can approach problems any way that you please in dynamic languages (such as the functional approach in the article) and, while they still really aren't for me, I completely understand why people use them.
Still, even Go has a prescribed way of approaching problems; as unusually broad as it is for a statically typed language. As a very relevant example, there is far more syntactical overhead in Go if you wish to fake FP (e.g. `func Foo(a A) func(b) func(c) func(d) e` to allow currying); where-as in JavaScript you simply bind or apply.
I've yet to see an article gushing over how natural functional is in Go (which is fine, Go isn't designed for that). This must be the 20th article I've seen on HN about functional in JavaScript.
Why would classes have any inherent performance benefits (and over what) ?
Here is a simple example of equivalent code in a class, function with a closure, and a prototype:
When instantiating a lot of objects, the prototype and class are practically the same, but the function closure pattern is a lot slower. On my machine, instantiating one hundred million of the class or prototype each took about 500ms, but the closure function took about 3s.