Skip to content

Latest commit

 

History

History
192 lines (138 loc) · 9.93 KB

ch2-ru.md

File metadata and controls

192 lines (138 loc) · 9.93 KB

Глава 2: Функции первого класса

Краткий обзор

Когда мы говорим о функциях «первого класса» мы имеем в виду то, что они такие же как и все остальные...[^учитель, поясните!]. С такими функциями можно обращаться так же, как и с любым другим типом данных, в функциях первого класса нет ничего особенного: их можно хранить в массивах, передавать в функции в качестве аргумента, присваивать переменным — всё, что душе угодно.

Пример ниже — это азы JavaScript. Тем не менее, поискав по коду на github, легко удостовериться, что большинство старательно игнорирует подобный подход. Соскучились по надуманным примерам? Пожалуйста:

var hi = function(name){
  return "Hi " + name;
};

var greeting = function(name) {
  return hi(name);
};

Здесь совершенно не нужно оборачивать hi в функцию greeting. Почему? Потому что в JavaScript функции являются вызываемыми. Если написать hi и добавить () на конце, то функция будет вызвана и вернёт какое-то значение. Если не дописывать скобки на конце, то будет возвращена сама функция, сохранённая в переменную. Убедимся в этом:

hi;
// function(name){
//  return "Hi " + name
// }

hi("jonas");
// "Hi jonas"

Поскольку greeting не делает ничего, кроме вызова hi с тем же самым аргументом, то можно написать проще:

var greeting = hi;


greeting("times");
// "Hi times"

Другими словами, hi — уже функция с одним аргументом, зачем же оборачивать её в ещё одну функцию, которая будет вызывать ту же hi с тем же аргументом? Бессмыслица какая-то. Это как зимой надеть шубу поверх тёплой куртки, при том, что вам и так тёпло.

Оборачивать функцию другой функцией просто для того, чтобы отложить её вызов — это не только слишком многословно, но ещё и считается плохой практикой (чуть ниже вы поймёте, почему, но намекну: речь идёт о поддержке кода).

Очень важно, чтобы вы поняли почему это так, прежде чем мы продолжим, поэтому позвольте мне привести несколько забавных примеров, которые я нашёл в существующих npm-пакетах:

// невежа
var getServerStuff = function(callback){
  return ajaxCall(function(json){
    return callback(json);
  });
};

// прозрел!
var getServerStuff = ajaxCall;

Мир JavaScript засорён подобным кодом. Вот почему оба примера выше — одно и то же:

// эта строка
return ajaxCall(function(json){
  return callback(json);
});

// равносильна этой
return ajaxCall(callback);

// перепишем getServerStuff
var getServerStuff = function(callback){
  return ajaxCall(callback);
};

// что эквивалентно следующему
var getServerStuff = ajaxCall; // <-- смотри, мам, нет ()

Вот так это делается. Ещё один пример, затем я объясню почему же я так настаиваю.

var BlogController = (function() {
  var index = function(posts) {
    return Views.index(posts);
  };

  var show = function(post) {
    return Views.show(post);
  };

  var create = function(attrs) {
    return Db.create(attrs);
  };

  var update = function(post, attrs) {
    return Db.update(post, attrs);
  };

  var destroy = function(post) {
    return Db.destroy(post);
  };

  return {
    index: index, show: show, create: create, update: update, destroy: destroy
  };
})();

Код этого контроллера нелеп на 99%, мы можем легко переписать его:

var BlogController = {
  index: Views.index,
  show: Views.show,
  create: Db.create,
  update: Db.update,
  destroy: Db.destroy
};

...или просто выкинуть его полностью, ведь он не делает ничего кроме объединения наших Views и Db.

Зачем отдавать предподчтение первому классу?

Хорошо, давайте обсудим причины использования именно функций первого класса. Как мы уже видели в примерах с getServerStuff и BlogController, добавить бесполезный уровень абстракции легко, но зачем? Это только увеличивает количество кода, который необходимо поддерживать и читать.

К тому же, если сигнатура внутренней функции поменяется, нам придётся также менять и внешнюю функцию.

httpGet('/post/2', function(json){
  return renderPost(json);
});

Если вдруг httpGet станет принимать новый аргумент err, то необходимо отредактировать и «функцию-склейку»:

// найти каждый вызов httpGet в приложении и добавить err
httpGet('/post/2', function(json, err){
  return renderPost(json, err);
});

Изменений потребовалось куда меньше, если бы мы с самого начала воспользовались функцией первого класса:

// rednerPost вызывается внутри httpGet с любым количеством аргументов
httpGet('/post/2', renderPost);  

Помимо определения лишних функций, нам также приходится придумывать названия аргументам, что само по себе не всегда так просто, особенно с ростом приложения.

Одной из частых проблем в проектах является как раз использование разных имён для одних и тех же понятий. Также стоит упомянуть момент с обобщением имён. Ниже обе функции делают одно и тоже, но последняя кажется более общей и, следовательно, более переиспользуемой:

// специфична для нашего конкретного приложения-блога
var validArticles = function(articles) {
  return articles.filter(function(article){
    return article !== null && article !== undefined;
  });
};

// более общая, легко переиспользовать в другом проекте
var compact = function(xs) {
  return xs.filter(function(x) {
    return x !== null && x !== undefined;
  });
};

Когда мы даём имена функциям, мы привязываем их к данным (в данным случае к articles). Это происходит чаще, чем кажется, и является источником изобретения многих «велосипедов».

Я должен также умопянуть, что как и при объектно-ориентированном подходе, нужно опасаться того, что this подкрадётся сзади и укусит вас за пятку. Если внутренняя функция использует this, а мы вызовем её как функцию первого класса, то почувствуем на себе весь гнев утечки абстракции.

var fs = require('fs');

// страшновато
fs.readFile('freaky_friday.txt', Db.save);

// не так страшно
fs.readFile('freaky_friday.txt', Db.save.bind(Db));

Вызвав bind, мы даём объекту Db возможность использовать мусорный код из его прототипа. Я стараюсь избегать this как грязных подгузников, да и в нём нет никакой необходимости, когда пишешь функциональный код. Однако, если вы собираетесь использовать внешние библиотеки, то не забывайте про безумный мир вокруг вас.

Некоторые могут поспорить, утверждая что this необходим с точки зрения производительности. Если вы из микро-оптимизаторов, пожалуйста, закройте эту книгу. Если вам не удастся вернуть за неё деньги, я надеюсь вы сможете её обменять на что-нибудь по-сложнее.

Теперь мы готовы продолжать.

Глава 3: Чистое счастье с Чистыми функциями