Продолжнаем изучать функциональное программирование и его основные концепции, а также учимся работать с кодом в функциональном ключе.
Рефакторинг
Взгляните на пример кода на JavaScript:
function validateSsn(ssn) {
if (/^\d{3}-\d{2}-\d{4}$/.exec(ssn))
console.log('Valid SSN');
else
console.log('Invalid SSN');
}
function validatePhone(phone) {
if (/^\(\d{3}\)\d{3}-\d{4}$/.exec(phone))
console.log('Valid Phone Number');
else
console.log('Invalid Phone Number');
}
Две эти функции практически идентичны. Вместо копирования и изменения validateSsn, давайте попробуем объединить функции в одну, а различия вынести в переменные. Введем значения value, regular expression и message:
function validateValue(value, regex, type) {
if (regex.exec(value))
console.log('Invalid ' + type);
else
console.log('Valid ' + type);
}
Параметры ssn и phone представлены в виде value. Регулярные выражения /^\d{3}-\d{2}-\d{4}$/ и /^\(\d{3}\)\d{3}-\d{4}$/ вынесены в regex. Части сообщения, отвечающие за вид проверки вынесены в type.
Иметь одну функцию лучше, чем две. И тем более, лучше, чем 3 и больше. Только представьте, какого будет искать ошибку, которая могла случайно закрасться в одну из десятков похожих функций во время ручных правок.
Но случается, что вы видите имеете такой код:
function validateAddress(address) {
if (parseAddress(address))
console.log('Valid Address');
else
console.log('Invalid Address');
}
function validateName(name) {
if (parseFullName(name))
console.log('Valid Name');
else
console.log('Invalid Name');
}
Здесь parseAddress и parseFullName – функции, которые принимают строку и возвращают true, если она разбирается. По аналогии с прошлым примером, мы можем ввести value для address и name, и type для ‘Address’ или ‘Name’. Но как передать в параметре функцию?
Функции высшего порядка
Многие языки программирования не поддерживают передачу функций в качестве параметра. В функциональном программировании, функция может передаваться в другую функцию наравне с переменными.
Хотя JavaScript и не является чистым функциональным языком, в нем можно проделывать некоторые функциональные трюки, в том числе, передавать функцию в качестве параметра.
Для нашего примера введем параметр parseFunc, в который будем передавать функции для обработки строк.
function validateValueWithFunc(value, parseFunc, type) {
if (parseFunc(value))
console.log('Invalid ' + type);
else
console.log('Valid ' + type);
}
То, что мы получили, называется функцией высшего порядка.
Теперь, мы можем вызывать новую функцию высшего порядка для каждого из четырех примеров функций выше, потому что в JavaScript Regex.exec будет также возвращать true, если строка пройдет проверку:
validateValueWithFunc('123-45-6789', /^\d{3}-\d{2}-\d{4}$/.exec, 'SSN');
validateValueWithFunc('(123)456-7890', /^\(\d{3}\)\d{3}-\d{4}$/.exec, 'Phone');
validateValueWithFunc('123 Main St.', parseAddress, 'Address');
validateValueWithFunc('Joe Mama', parseName, ‘Name');
Теперь код выглядит лучше, но регулярные выражения все еще занимают много места. Давайте исправим и это:
var parseSsn = /^\d{3}-\d{2}-\d{4}$/.exec;
var parsePhone = /^\(\d{3}\)\d{3}-\d{4}$/.exec;
validateValueWithFunc('123-45-6789', parseSsn, 'SSN');
validateValueWithFunc('(123)456-7890', parsePhone, 'Phone');
validateValueWithFunc('123 Main St.', parseAddress, 'Address');
validateValueWithFunc('Joe Mama', parseName, ‘Name');
Код стал опрятней, но регулярных выражений может потребоваться значительно больше, чем два. И для каждого такого выражения нужно создавать отдельную переменную и не забыть добавить в конце .exec, а забыть об этом очень просто. Давайте снова улучшим код, чтобы еще немного облегчить себе жизнь:
function makeRegexParser(regex) {
return regex.exec;
}
var parseSsn = makeRegexParser(/^\d{3}-\d{2}-\d{4}$/);
var parsePhone = makeRegexParser(/^\(\d{3}\)\d{3}-\d{4}$/);
validateValueWithFunc('123-45-6789', parseSsn, 'SSN');
validateValueWithFunc('(123)456-7890', parsePhone, 'Phone');
validateValueWithFunc('123 Main St.', parseAddress, 'Address');
validateValueWithFunc('Joe Mama', parseName, 'Name');
Так, мы создали функцию, которая принимает регулярное выражение и возвращает функцию для парсинга с .exec на конце.
Функция, как результат работы функции
Это незначительное улучшение, но хороший пример функции высшего порядка, которая возвращает другую функцию.
Вот еще один пример такой функции:
function makeAdder(constantValue) {
return function adder(value) {
return constantValue + value;
};
}
Здесь функция makeAdder принимает constantValue и возвращает adder, функцию, которая прибавляет константу к любому значению, которое получает. Вот как можно это использовать:
var add10 = makeAdder(10); console.log(add10(20)); // выведет 30 console.log(add10(30)); // выведет 40 console.log(add10(40)); // выведет 50
Мы создали функцию add10, передав константу 10 функции makeAdder, которая вернет значение + 10 от любого исходного.
Обратите внимание, что adder имеет доступ к constantValue даже после того, как makeAddr вернула значение. Так происходит потому, что constantValue была в своей области, когда adder была создана.
Такое поведение важно, потому что без него функции, возвращающие функции были бы не так полезны. Важно понимать как это работает и как называется такое поведение.
Это называется «замыкания».
Замыкания
Ниже показан выдуманный пример функции, которая использует замыкания.
function grandParent(g1, g2) {
var g3 = 3;
return function parent(p1, p2) {
var p3 = 33;
return function child(c1, c2) {
var c3 = 333;
return g1 + g2 + g3 + p1 + p2 + p3 + c1 + c2 + c3;
};
};
}
В этом примере child имеет доступ к своим переменным, переменным родителя и переменным родителя родителя.
parent имеет доступ к своим переменным и переменным своего родителя.
grandParent имеет доступ только к своим переменным.

Так это можно использовать:
var parentFunc = grandParent(1, 2); // вернет parent() var childFunc = parentFunc(11, 22); // вернет child() console.log(childFunc(111, 222)); // выведет 738 // 1 + 2 + 3 + 11 + 22 + 33 + 111 + 222 + 333 == 738
Здесь parentFunc удерживает область parent с того момента, как grandParent возвращает parent.
Также, childFunc удерживает область child с того момента, как parentFunc, которая просто parent, возвращает child.
Когда функция создана, все переменные в ее области во время создания доступны на время жизни функции. Функция существует столько, сколько существуют ссылки на нее. Например, область функции child существует, пока на нее ссылается childFunc.
Замыкания – полезный инструмент, но в JavaScript он может приносить проблемы, когда переменные меняют значения. К счастью, в функциональном программировании переменные значения не меняют.
Комментарии