Композиция функций
Вот еще пример простого приложения — опросника.
Необходимо ответить на каждый блок. Каждый блок содержит несколько вопросов. После ответа на один блок, мы переходим к следующему.
Каждый блок можно представить как панель, которая может быть в режиме вопроса, режиме результата или неактивной.
В каждой панели могут быть разные поля. Строковые, числовые, даты. Поля могут быть в двух режимах — редактирования или результата.
Посмотрим, как можно подойти к решению этой задачи, используя функциональный подход.
Помните нашу любимую функцию prop?
tweeps.map(prop("name"));
У нее есть брат-близнец func.
tweeps.map(func("to.String"));
Она возвращает функцию, которую вы можете применять к объектам.
Теперь посчитаем результат каждого блока в опроснике
buildSummary: function(){
return div(this.components.map(func("buildSummary")));
}
Принцип должен быть очевиден. Мы возвращаем div, в котором будут элементы, созданные функциейbuildSummary для каждого блока опросника.
В этом примере каждый компонент сам знает как представить свой результат. Но иногда панель должна отобразить результат специфическим образом.
Поэтому мы можем написать 2 функции: buildSummary и getSummary.
Первая — строит полное представление, включая html теги. Вторая — возвращает объект, который содержит необходимые результаты.
И как только нам понадобилась хитрая обработка результатов, вся красота начала рушиться.
buildSummary: function(){
var div = document.createElement("div");
for(var i =0; l=this.components.length; i<l; ++i)
{
p = document.CreateElement("p");
p.innerHTML = this.components[i].getSummary().text;
div.appendChild(p);
}
return div;
}
Однако, мы уже достаточно функционально ориентированы, чтобы улучить этот кусок кода. Первое очевидное улучшение — применить foreach.
buildSummary : function(){
var div = document.createElement("div");
this.components.forEach(function(component){
var p = document.createElement("p");
p.innerHTML = component.getSummary().text;
div.appendChild(p);
});
return div;
}
Мы избавились от переменных цикла, но возможно ли использовать map?
buildSummary : function(){
return div(this.components.map(function(component){
var p = document.createElement("p");
p.innerHTML = component.getSummary().text;
return p;
}));
}
Коротко, но далеко до идеала. Основная проблема в этом выражении:
component.getSummary().text;
Проблема в том, тут происходит не одна, а целых три вещи:
- Получение результата через getSummary()
- Получение свойства text
- Оборачивание результата в тег p
А как насчет нескольких функций map?
buildSummary: function() {
return div(this.components.
map(function(component){
return component.getSummary();
}).map(function(summary){
return summary.text;
}).map(function(text){
var p = document.createElement("p");
p.innerHTML = text;
return p;
}));
}
Функциональный стиль налицо, но выглядит страшно. И читать очень неудобно.
Но давайте глянем на код еще разок. Что у нас здесь?
return component.getSummary();
Здесь мы вызываем метод объекта. Но ведь мы создали специальную функцию для этого, func.
buildSummary: function() {
return div(this.components.
map(func("getSummary")).
map(function(summary){
return summary.text;
}).map(function(text){
var p = document.createElement("p");
p.innerHTML = text;
return p;
}));
}
А здесь?
function(summary){
return summary.text;
}
Мы получаем доступ к свойству объекта. И для этого тоже есть удобная функция.
buildSummary: function() {
return div(this.components.
map(func("getSummary")).
map(prop("text")).
map(function(text){
var p = document.createElement("p");
p.innerHTML = text;
return p;
}));
}
Остался последний участок.
function(text){
var p = document.createElement("p");
p.innerHTML = text;
return p;
}
Мы здесь создаем DOM элемент и устанавливаем его внутреннее свойство. У нас есть что-то похожее в нашем DSL, не правда ли?
buildSummary: function() {
return div(this.components.
map(func("getSummary")).
map(prop("text")).
map(p));
}
Теперь почти красиво. Но есть один нюанс. Мы делаем 3 прохода по списку. В каких-то случаях это может быть нормально, но в целом несколько неоптимально. Что же можно сделать?
Пора использовать композицию функций. Мы хотим заставить одну функцию делать то, что делают три.
var summarize = compose(
[p, prop("text"), func("getSummary")]);
|