Anyone who has done a minimum of JavaScript development is aware to a greater or lesser extent of how many oddities, quirks, gotchas... this beautiful language and its base library have. Coercion, Date.getMonth, parseInt, null and undefined... just to name a few. With the advent of ES5, a few more oddities joined the list, bringing some more pleasure and pain to our developer's life. I'll muse, praise and rant on some of them in this post.
Function.bind
When years ago prototype.js brought up this function I really appreciated it, mainly for the lexial this functionality, but over the years I've got so used to creating a closure myself to bind this that I gradually quit using it, so its addition to the ES5 standard went quite unnoticed to me. The other day, while reading this excellent article about the arrow functions coming to ES.next, it caught my attention that ES5 bound functions are a new type of functions (they have extra and overloaded "internal properties" like bound, ... So, they are different from the normal wrapper function that prototype.js or any ES5 shim would return. You can read more about bound functions here.OK, enough preambles, why am I including Function.bind in this post?
Well, apart from binding this Function.bind also allows for the binding of other parameters, what we commonly know as Partial Function Application. Good, two birds with one stone. Problem is that it does not allow for binding parameters but keeping the dynamic binding for this. If for example you bind a null or undefined value for this:
var func2 = func1.bind(null, "value1";
if you later on invoke the bound function like this:
myObj.func2();
func2 won't get passed myObj, but null...
As I don't see any use in binding null to a function that is really using this I think the correct/useful behaviour would be to consider that when a bound function hosts a null or undefined value in [[BoundThis]], it means that it's not bound.
As unfortunately that's not the case, we would need a separate function providing Partial Function Application (like the one provided by prototype.js and wrongly named curry...). The thing is that such a function does not exist in the standard library and once again we'll need to write our own (which is trivial, but the thing is that such miss tastes to me as having an incomplete feature.
Lack of Object.extend
This functionality is so useful that I really can't understand why it has not been added to the standard library. Sure it's easy to implement your own, or just use this or this, but really, why not add it to the standard?New Object functions
Most of the new Object functions added in ES5 (freeze, seal, getOwnPropertyDescriptor, defineProperty, keys...) has been added as "static methods" (Object.method) rather than "instance methods" (Object.prototype.method). This has seemed confusing to me since they lifted their (beautiful) head (all these are operations applied to an instance, and I would expect them to belong to the instance). When I finally made my mind to post a question in StackOverflow seeking for others insight, I realized others had already asked and answered it. These 2 points seem key to the explanation:
Cleanly separate the meta and application layers
Try to minimize the API surface area (i.e., the number of methods and the complexity of their arguments)
If we think in terms of .Net or Java and Reflection, "meta operations" are neither instance or static methods of Object, but instance methods of Type/Class, so the JavaScript approach is a bit different, but makes sense to me.
Anyway, even with this explanation in mind, some cases still look confusing to me:
- Dealing with the internal prototype ([[Prototype]] does not seem to be considered as a meta operation, as we have Object.prototype.isPrototypeOf, but then, why do we have Object.getPrototypeOf? Having something called Object.prototype.getPrototype would look like more consistent to me.
- I can't see why we have Object.prototype.hasOwnProperty when other property related methods like getOwnPropertyDescriptor, defineProperty are defined as "static" ones.
All this instance vs static thing, has made look back for a while. Years ago, in a period when I was quite much into Python, I was quite puzzled by the presence of a string.upper function. Doing string.upper("my string") rather than "my string".upper() was new and very "old school" to me. This seemed quite unrelated to the well understood decision making for "static method" vs "instance method": clearly operating on a concrete instance.
At the time I found some good explanation, and trying to find it today, I've found the same idea, but expressed from the .Net world.
Developers new to .NET tend to be surprised that calling ToUpper on a string doesn't convert it to upper case. And I think it's perfectly reasonably to be surprised. ToUpper looks like an imperative operation. Its form actively conceals the fact that it's really a function in the strict functional programming sense - it's has no side effects, it merely takes an input expression returns a result based on that input expression without modifying the input in any way. The input expression is the implicit this reference - the object on which ToUpper is invoked.
Thinking about it again now, the rationale behind having Math.sin, Math.cos... rather than Number.prototype.sin, Number.prototype.cos could be in part that (apart from the Single Responsibility Principle and avoiding boxing)
No comments:
Post a Comment