When I use classes, when I don’t, what I do instead and why
blog.kentcdodds.com
blog.kentcdodds.com
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:
async.timeout(server.close, 60000)(exit)
And one way to fix: async.timeout(cb => server.close(cb), 60000)(exit)
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...
const closeServer = () => server.close();
async.timeout(closeServer, 60000)(exit);
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.
var that = this
just to be sureI'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}
class X {
id = (x) => x;
}
See here: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.
Weird.
"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.
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.
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.
I've been doing it for a while and haven't had any problems and have really enjoyed doing it.
Inheritance, overrides, super calls? The less I use those, the less I miss them.
So, 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.
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.
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:
class FooClass {
constructor (bar) { this.bar = bar; }
getBar () { return this.bar; }
setBar (bar) { this.bar = bar; }
}
function FooClosure (bar) {
var _bar = bar;
return {
getBar () { return _bar; },
setBar (bar) { _bar = bar; }
};
}
function FooPrototype (bar) { this.bar = bar; }
FooPrototype.prototype.getBar = function () { return this.bar; };
FooPrototype.prototype.setBar = function (bar) { this.bar = bar; };
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.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 love how much I can do with JavaScript without classes. Same with Python! You'd be surprised how often you don't need them.
class Foo {
bind function bar() {
console.log(this);
}
}
So behind the scene it would do `this.bar = this.bar.bind(this);`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?
class Foo {
bar = () => {
console.log(this);
}
}
Has to be compiled with Babel, though. class Foo {
bar = () => {
console.log(this);
}
}
This basically puts `this.bar = () => console.log(this)` in the constructor, and since it's an arrow function it captures the correct context.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).
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: my_function(3) becomes
my_function(my_test_function_context, 3);
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 was invoked from the f member of o.Similarly, the this binding is only affected by the most immediate member reference. In the following example, when we invoke the function, we call it as a method g of the object o.b. This time during execution, this inside the function will refer to o.b. The fact that the object is itself a member of o has no consequence; the most immediate reference is all that matters.
== "this" on the object's prototype chain ==
The same notion holds true for methods defined somewhere on the object's prototype chain. If the method is on an object's prototype chain, this refers to the object the method was called on, as if the method were on the object.
== "this" with a getter or setter ==
Again, the same notion holds true when a function is invoked from a getter or a setter. A function used as getter or setter has its this bound to the object from which the property is being set or gotten.
== As a constructor ==
When a function is used as a constructor (with the new keyword), its this is bound to the new object being constructed.
While the default for a constructor is to return the object referenced by this, it can instead return some other object (if the return value isn't an object, then the this object is returned).
== As a DOM event handler ==
When a function is used as an event handler, its this is set to the element the event fired from (some browsers do not follow this convention for listeners added dynamically with methods other than addEventListener).
== In an in–line event handler ==
When code is called from an in–line on-event handler, its this is set to the DOM element on which the listener is placed:
<button onclick="alert(this.tagName.toLowerCase());">
Show this
</button>
The above alert shows button. Note however that only the outer code has its this set this way: <button onclick="alert((function() { return this; })());">
Show inner this
</button>
In this case, the inner function's this isn't set so it returns the global/window object (i.e. the default object in non–strict mode where this isn't set by the call).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.
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.> 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.
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.
function setTimeout(callback, delay) { do the set timeout thing, at the right time, call callback(); }
You pass a function argument to it, but you're not calling that function, so you haven't passed the `this` argument: setTimeout(someObject.someFn, delay)
From within setTimeout, the code gets that function and calls it without anything left of the dot callback();
So the `this` argument gets the default value in that environment (undefined/global/window)Calling bind on a bound function is also explained by the simplistic model:
function bind(f, o) { return function(...args) { return o::f(...args) } }
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.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.
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.
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...
finishWork().then(placeOrder with deliveryService)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:
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 nothingThat 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
>>> class Foo:
>>> def __init__(self, x): self.x = x
...
... def bar(self): print('x =', self.x)
...
...
>>> f = Foo(23)
>>> b = f.bar
>>> b()
x = 23
Note that when you extract the method from the object as a callable, it hangs onto the right self. Same deal as Java: class Foo {
private int x;
Foo(int x) { this.x = x; }
void bar() { System.out.println("x = " + x); }
}
public static void main(String[] args) {
Foo f = new Foo(23);
Runnable b = f::bar;
b.run(); // prints x = 23
}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`
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!"