Let me start with this statement: the use of anonymous functions make your code harder to read and, can erode your design. An anonymous function is a function that’s never bound to an identifier. I prefer to define it as ‘a function that has no name’ as Javascript can have anonymous functions that can still have a ‘name’:
var my_awesome_function = function() {
return true;
};
my_awesome_function();
The above function is anonymous, but has a name my_awesome_function
and that name can be changed through assignment.
var my_lesser_function = my_awesome_function;
The important thing here, is whether the function has an identifiable name when I’m reading the code. Anonymous functions have no name during debug. Here is an example of some code that has four anonymous functions, but we’re going to focus on the outermost function, I’d tell you what it’s called but… you know. You may remember this code from the Issue 02’s Improve This.
var start_streaming = function(db, endpoint, params) {
Twitter.stream(endpoint, params, function(stream) {
stream.on('data', function(data) {
if(data.user && data.text) {
db.collection('tweets', function(err, tweets_collection) {
tweets_collection.insert(data);
});
db.collection('events', function(err, events_collection) {
map_tweet_to_event(data, function(evt) {
events_collection.insert(evt);
});
};
});
});
};
The issue I have here is that it’s not obvious what this code does. I can work it out but I’d rather not have to. So my question is, could we have named it? This would be a challenge because this function is doing streaming, validation and persistence. Trying to name something that does more than one thing is hard. Naming functions is already hard without this added complexity.
A well named function should do what it says. An anonymous function makes no such promise. When new behaviour comes it can be easily slipped into an anonymous function and everything still ‘makes sense’. I prefer to see it as: anonymous functions make it easier to avoid the pain of naming a function. “I can just use an anonymous function here”. The pain gets deferred to the person who later has to read and understand the function.
In the Improve This article, the function ended up being split into two parts. The first part performs steaming and validation. It also accepts a callback which it invokes on valid entries.
var stream_tweets = function(terms, callback) {
endpoint = 'user'
params = {'with': 'followings', 'track': terms};
Twitter.stream(endpoint, params, function(stream) {
stream.on('data', function(data) {
if(data.user && data.text) {
callback(data);
}
});
});
};
var record_tweet = function(db) {
return function(data) {
db.collection('tweets', function(err, tweets_collection) {
tweets_collection.insert(data);
});
db.collection('events', function(err, events_collection) {
events_collection.insert(map_tweet_to_event(data));
});
}
};
The first function is now better named and the callback, with different concerns, is now named and self-contained.
Now, not all anonymous functions are bad. An example of when having a named function doesn’t add much is when you’re passing a predicate to a well named function or you want to make good use of your closure.
things.sort {|a,b| a.age <=> b.age && a.sort_name <=> b.sort_name }
But even then, I still find better ways to express the same thing.
sort_by_age_then_name = Proc.new do | a, b |
a.age <=> b.age && a.sort_name <=> b.sort_name
end
things.sort(&sort_by_age_then_name)
The next time you’re writing an anonymous function consider if it needs a name. Or, if you are extending an existing function, consider if it still does what it says on the tin. Sometimes you might find that what you are trying to do, already exists, with a good name:
words = []
file.readlines.each { |line| words << line.strip }
Is also:
words = File.readlines('word_list').map(&:chomp)