Function composition


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

Есть вот такие вот умные определения:

«Композиция» — (от лат. compositio — составление — связывание) объединение, составление, сопоставление, расположение, сложение, соединение частей в единое целое в определенном порядке.

В математике композиция функций (суперпози́ция фу́нкций) — это применение одной функции к результату другой.

На деле вы постоянно встречали подобный прием:


const x1 = 1;
const x2 = add(x1, 1);
const x3 = add(x1, 2);
const x4 = add(x1, 3);
const result = x4;

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


const result = add(3, add(2, add(1, 1)));

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

В функциональных языках есть функции помощники, они берут на себя заботу о лишней вложенности нашей функции, и как работяги на стройке передают один кирпичик другому рабочему. Разберем функцию pipe и compose из моей любимой рамды конечно же =) Перепишем пример выше:


const result = pipe(
    add(1), // 1 => n => 1 + n;
    add(2), // 2 => n => 2 + n;
    add(3), // 3 => n => 3 + n;
)
result(1);

Не трудно догадаться что делает функция с название труба =) она как водопроводная труба передает данные из начала в конец, добавляя наши примеси где нужно. Вы же помните нашу каррированую функцию add = x => y => x + y? мы частично вызвали функцию добавления, и все что осталось нам, передать в нее последний аргумент, чтобы она завелась. Вот так вот схематически выглядит функция pipe:


function pipe (f,g,j) {
  return function(x) {
    return j(g(f(x)));
  };
};

0 ноль магии, всю заботу о нечеловекочитаемой сосиске взяла на себя она. с функцией add то все понятно, она меленькая, простая, тупая. тут не интересно, нужно больше практики, Вчера я добавил в своего бота для телеграм возможность рандомного выбора темплейта для сообщения, и вот как я это сделал


const STRINGS = [
  `Тут *|name|* \nРешил заказать песню *|track|*`,
  `Ну зачем ты *|name|* \nпросишь эту фигню? *|track|*`,
  `Вадик, тут твой друган *|name|* \nопять фигню просит *|track|*`,
  `Вова, ты знаешь этого чела? *|name|* \nон пестню хочет *|track|*`,
  `Я *|name|* \nжелаю поплясать под *|track|*`
];

const randomTpl = arr => arr[Math.floor(Math.random() * arr.length)];

const stringTpl = ({ name, track }) => pipe(
    randomTpl, // передаем массив строк, и выбираем 1 случайную
    replace("|track|", track), // функция реплейс из рамды, принимает 3 аргумента: шаблон, строка на которую менять, и ПОСЛЕДНИМ аргументом строку в которой менять
    replace("|name|", name), // все функции тут возвращают НОВОЕ значение, и никак не мутируют старое
)(STRINGS);

Теперь этот бот умеет ругаться по разному, и почти в функциональном стиле =)

Давайте еще пример, украл задачу с кодварс, нужна функция проверяющая является ли слово палиндромом

Слово или фраза, которые одинаково читаются слева направо и справа налево.

Решений в интернете полно, но мы ща очень лаконично напишем свое:


const isPalindrom = str => {
    // сначала нужно развернуть строку
    const reverseString = pipe(
        split(''), // конвертируем строку в массив
        reverse, // разворачиваем массив
        join('') // склеиваем обратно
    );
    return str === reverseString(str);
}

Лучшей читаемости я наверно не видал в жизни, каждый шаг это чистая функция которая делает всего 1 вещь, и передает свой результат дальше. Представьте как круто то, что вы можете на создавать таких промежуточных функций, и так же склеивать их в 1 с помощью pipe и compose;

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


compose(
    C,
    B,
    A,
)('kek');

Итак лонгрид опять получился, на неделе покидаю интересных примеров, а может быть и задачек маленьких =)) Следующий раз покажу как можно использовать оч крутую функцию evolve из рамды