Skip to content

Commit 304ab4f

Browse files
committed
Merge pull request #75 from d8uv/master
Detecting and Creating Missing Functions rewrite
2 parents 5559d9e + e9076fe commit 304ab4f

File tree

1 file changed

+40
-5
lines changed

1 file changed

+40
-5
lines changed

Diff for: chapters/metaprogramming/detecting-and-replacing-functions.md

+40-5
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ You want to detect if a function exists and create it if it does not (such as an
99

1010
## Solution
1111

12-
Use `::` to detect the function, and assign to it if it does not exist.
12+
Use the existential assignment operator (`?=`) to assign a function to the classes' prototype (using the `::` shorthand), and wrap it all in a IIFE (`do ->`) to contain the variables.
1313

1414
{% highlight coffeescript %}
15-
unless Array::filter
16-
Array::filter = (callback) ->
17-
element for element in this when callback element
15+
do -> Array::filter ?= (callback) ->
16+
element for element in this when callback element
1817

1918
array = [1..10]
2019

@@ -24,4 +23,40 @@ array.filter (x) -> x > 5
2423

2524
## Discussion
2625

27-
Objects in JavaScript (and thus, in CoffeeScript) have a prototype member that defines what member functions should be available on all objects based on that prototype. In CoffeeScript, you can access the prototype directly via the `::` operator.
26+
Objects in JavaScript (and thus, in CoffeeScript) have a prototype member that defines what member functions should be available on all objects based on that prototype.
27+
In Coffeescript, you access this prototype using the `::` shortcut. So, if you want to add a filter function to the array class, you do `Array::filter = ...`. This will add the filter function to all arrays.
28+
29+
However, we don't ever want to overwrite a prototype that we haven't created in the first place. For example, if `Array::filter` already exists in a fast native form in the browser, or a library maker has their own specific version of `Array::filter`, then you'll either replace the quick native version with a slow Javascript version, or you will break the library that depends on their own Array::shuffle.
30+
What you need to do is only add the function if it doesn't already exist. That's where the existential assignment operator (`?=`) comes in. If we do `Array::filter ?= ...` instead, it will see if `Array::filter` already exists. If it does, then it will use the current version. If it doesn't, it will add yours.
31+
32+
Finally, because the existential assignment operator--when compiled--creates a few variables, we clean up the code by wrapping it in an [Immediately-Invoked Function Expression (IIFE)](http://benalman.com/news/2010/11/immediately-invoked-function-expression/). This hides those internal-use-only variables from leaking outside. So, if the function we're writing already exists, it runs, does basically nothing, and exits, affecting absolutely none of your code. But, if the function we're writing *doesn't* exist, we send out only the function we're writing as a closure, so only the function you've made affects the code. The internal workings of `?=` are hidden either way.
33+
34+
### Example
35+
36+
Below, we've compiled and annotated the coffeescript written in the solution above
37+
38+
{% highlight javascript %}
39+
// (function(){ ... })() is an IIFE, compiled in thanks to `do ->`
40+
(function() {
41+
42+
// This is from the `?=` operator, used to check if Array.prototype.filter (`Array::filter`) exists.
43+
// If it does, we set it to itself, and return. If it doesn't, then we set it to the function, and return the function.
44+
// The IIFE is only used to hide _base and _ref from the outside world.
45+
var _base, _ref;
46+
return (_ref = (_base = Array.prototype).filter) != null ? _ref : _base.filter = function(callback) {
47+
48+
// `element for element in this when callback element`
49+
var element, _i, _len, _results;
50+
_results = [];
51+
for (_i = 0, _len = this.length; _i < _len; _i++) {
52+
element = this[_i];
53+
if (callback(element)) {
54+
_results.push(element);
55+
}
56+
}
57+
return _results;
58+
59+
};
60+
// The end of the IIFE from `do ->`
61+
})();
62+
{% endhighlight %}

0 commit comments

Comments
 (0)