Как функциональное программирование может сделать нашу жизнь проще

Сергей Ткаченко, ЯндексМаркет

Как функциональное программирование может сделать нашу жизнь проще

Сергей Ткаченко,
Разработчик интерфейсов

 

Функциональное программирование

Функциональное программирование

 

Что такое ФП?

 

Это набор идей, а не чёткие инструкции и действия

Основная идея

Используем функции

Функции

Характеристики функций:

Функции


var calculateTotalPrice = data => {
  // ...
};

fs.readFile('./offers.csv', 'utf8', calculateTotalPrice);

Функции

Характеристики функций:

Функции


var someGlobalFlag = true;

var swapState = data => {
    if (data.needSwapState) {
        someGlobalFlag = !someGlobalFlag;
    }
};

Функции


var someGlobalFlag = true;

var swapState = (data, flag) => {
    if (data.needSwapState) {
        return flag ? true : false;
    }
};

var newGlobalFlag = swapState(data, someGlobalFlag);

Функции

Характеристики функций:

Функции


var toUpperCaseAll = (list) => {
    for (let i = 0; i < list.length; i++) {
        list[i] = list[i].toUpperCase();
    }

    return list;
};

toUpperCaseAll(['mike', 'leo', 'raf', 'don']);
// ['MIKE', 'LEO', 'RAF', 'DON']

Функции


var toUpperCaseAll = (list) => {
    var result = [];

    for (let i = 0; i < list.length; i++) {
        result[i] = list[i].toUpperCase();
    }

    return result;
};

toUpperCaseAll(['mike', 'leo', 'raf', 'don']);
// ['MIKE', 'LEO', 'RAF', 'DON']

Функции

Характеристики функций:

Функции


var add = (a, b) => a + b;

add(2, 5) // 5
add(2, 5) // 5
add(2, 5) // 5

 

Чистые функции

Основная идея №2

Используем только функции

 

Используем функции для:

Расчёты


a + b



var add = (a, b) => a + b;

add(2, 5);

Переменные


var five = 5;



var five = () => 5;

Циклы


var transformList = (callback, list) => {
    var result = [];

    for (let value of list) {
        let newValue = callback(value);

        result.push(newValue);
    }

    return result;
}

Циклы


function transformList(callback, [x, list]) {
    if (!x) return [];

    return [callback(x),  . . . transformList(callback, list)];
}

 

Изменение состояния программы

Изменение состояния программы

Замыкания


var add = function (x) {
    return function (y) {
        return x + y;
    };
};

var increment = add(1);
var addTen = add(10);

Каррирование


var add3 = (a, b, c) =>  a + b + c;
var add3Curryed = curry(add3);

add3Curryed(1, 2, 3); // 6
add3Curryed(1)(2, 3); // 6
add3Curryed(1, 2)(3); // 6
add3Curryed(1)(2)(3); // 6

Каррирование


curry = func => {
    var arity = func.length;

    return function f1(. . . args) {
        if (args.length >= arity) {
            return func.apply(null, args);
        } else{
            return function f2(. . . nargs) {
              return f1.apply(null, args.concat(nargs));
            };
        }
    };
};

Композиция


var capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);
var trim = str => str.trim();


var formatStr = compose(capitalize, trim);

Композиция


var split = curry((separator, str) => str.split(separator));
var join = curry((separator, list) => list.join(separator));
var map = curry((func, list) => list.map(func));
var toLowerCase = str => str.toLowerCase();


var toSlug = compose(
  encodeURIComponent,
  joinString('-'),
  map(stringToLowerCase),
  splitString(' ')
);

Композиция


var compose = (. . . funcs) =>
    (value) => funcs.reverse().reduce((v,fn) => fn(v), value);

 

Какие плюсы мы получаем?

 

Как это использовать в реальной жизни?

Мы живём в мире сайд-эффектов

Инструменты

fantasy-land

Идеальный мир


var getRows = pluck('rows');
var getNames = pluck('name');

var renderUsersTable = Engine.render('users-table');

var getUsersFromDb = compose(getRows, User.findAll);

var cleanUpData = compose(capitalise, getNames);

var makePage = compose(renderUsersTable, map(cleanUpData), getUsersFromDb);


makePage( {limit: 20} );

 

Контейнеры

Контейнеры


var Container = function(x) {
    this.__value = x;
};

Container.of = x => new Container(x);

Container.prototype.map = func => {
    return Container.of(func(this.__value));
};

Контейнеры


Container.of(2).map(function(two){ return two + 2 });
// Container(4)

 

NullPointerException

NullPointerException


var getUsersFromDb = compose(getRows, User.findAll);

NullPointerException


var getUsersFromDb = compose(getRows, Maybe.of, User.findAll);

NullPointerException


var getUsersFromDb = compose(map(getRows), Maybe.of, User.findAll);

getUsersFromDb() // Maybe([{name: 'leo', name: 'don']);
getUsersFromDb() // Maybe(null);

Maybe


var Maybe = function(x) {
    this.__value = x;
}

Maybe.of = function(x) {
    return new Maybe(x);
}

Maybe.prototype.isNothing = function() {
    return (this.__value === null || this.__value === undefined);
}

Maybe.prototype.map = function(f) {
    return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
}

 

Обработка ошибок

Обработка ошибок


var renderUsersTable = Engine.render('users-table');

var makePage = compose(
  map(renderUsersTable),
  map(cleanUpData),
  getUsersFromDb
);

Обработка ошибок


var errorMessage = 'sorry there is no users';
var errorContainer = Either.Left(errorMessage);

var getUsers = cond([
    [isArrayLike, Either.Right],
    [T, always(errorContainer)]
]);

var getUsersFromDb = compose(map(getRows), getUsers, User.findAll);

Обработка ошибок


var renderUsersTable = Engine.render('users-table');
var renderErrorMessage = Engine.render('error-message');

var render = Either.either(renderErrorMessage, renderUsersTable);

Обработка ошибок


var makePage = compose(render, getUsersFromDb);

 

Асинхронность

Асинхронность


var getUsersFromDb = compose(getRows, User.findAll);

Асинхронность


var findAll = params =>
    Future((reject, resolve) =>
        User.findAll(params)
            .then(resolve)
            .catch(reject));

var getUsersFromDb = compose(map(getRows), findAll);

var makePage = compose(map(cleanUpData), getUsersFromDb);

Асинхронность


var page = makePage({limit: 20});

page.fork(renderErrorMessage, renderUsersTable);

 

Всё вместе

Всё вместе


var getRows = pluck('rows');
var getNames = pluck('name');
var cleanUpData = compose(capitalise, getNames);

var renderUsersTable = Engine.render('users-table');
var renderErrorMessage = Engine.render('error-message');

var render = Either.either(renderErrorMessage, renderUsersTable);

Всё вместе


var render = . . .

var errorMessage = 'sorry there is no users';
var errorContainer = Either.Left(errorMessage);

var getUsers = cond([
  [isArrayLike, Either.Right],
  [T, always(errorContainer)]
]);

var findAll = params =>
    Future((reject, resolve) =>
        User.findAll(params).then(resolve).catch(reject));

var getUsersFromDb = compose(flatMap(getRows), map(getUsers), findAll);

Всё вместе


var render = . . .
var getUsersFromDb = . . .

var makePage = compose(map(render), flatMap(cleanUpData), getUsersFromDb);

var page = makePage({limit: 20});

page.fork(logError, insertToDOM);

 

Homer Simpson. ©1999 20TH CENTURY FOX FILM CORP.

Плюсы

Минусы

Полезные ссылки

Контакты

Сергей Ткаченко

Разработчик интерфейсов

ts1503@yandex-team.ru