Skip to content

Commit

Permalink
Detecting and Creating Missing Functions rewrite
Browse files Browse the repository at this point in the history
New solution, and greatly expanded discussion section 
to explain it.

From:
  unless Class::member
    Class::member = contents

To:
  do -> Class::member ?= contents
  • Loading branch information
d8uv committed Mar 11, 2013
1 parent 5559d9e commit d082571
Showing 1 changed file with 10 additions and 5 deletions.
15 changes: 10 additions & 5 deletions chapters/metaprogramming/detecting-and-replacing-functions.md
Expand Up @@ -9,12 +9,11 @@ You want to detect if a function exists and create it if it does not (such as an

## Solution

Use `::` to detect the function, and assign to it if it does not exist.
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.

{% highlight coffeescript %}
unless Array::filter
Array::filter = (callback) ->
element for element in this when callback element
do -> Array::filter ?= (callback) ->
element for element in this when callback element

array = [1..10]

Expand All @@ -24,4 +23,10 @@ array.filter (x) -> x > 5

## Discussion

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.
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 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.

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.
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.

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.

1 comment on commit d082571

@sukima
Copy link

@sukima sukima commented on d082571 Mar 11, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to provide compiled JavaScript snippits? Or is that an exercise for the student (not sure how other pages handle that).

This is a great trick and well worth knowing (had to use it myself today). The discussion section is well worded only I can see a newer user reading it and getting confused (hence maybe JavaScript examples to show the difference between them)

Other then that I say :shipit:

Please sign in to comment.