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

Алексей Распопов

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

Кто я?

Дисклеймер

Проблема

Claude Shannon:
A Mathematical Theory of Communication

Система коммуникации

Unix pipes

Вы уже знакомы с конвеерами?

				$ cat file.txt | grep ^boo | foo
			

Трубы

Нам нужен какой-нибудь способ соединять программы как садовые шланги — вкручивать новые сегменты когда необходимо обрабатывать информацию по-другому. Это так же применимо к I/O.

Doug McIlroy, 1964

Обычное дело для JavaScript

				var numbers = [1, 2, 3, 4, 5, 6, 7, 8, ...];
			
				numbers
				    .map((n) => n * n)
				    .filter((n) => n % 2 === 0)
				    .reduce((a, b) => a + b);
			

Расширяемое решение

				var numbers = [1, 2, 3, 4, 5, 6, 7, 8, …];
			
				numbers
				    .map((n) => n * n)
				    .filter((n) => n < 40)
				    .filter((n) => n % 2 === 0)
				    .reduce((a, b) => a + b);
			

Вместо императивного ужаса

				var numbers = [1, 2, 3, 4, 5, 6, 7, 8, …], sum = 0, num;
				for(var i = 0; i < numbers.length; i++){
				    num = numbers[i];
				    num *= num;
				    if(num % 2 === 0){
				        sum += num;
				    }
				}
			

И что оно делает?

				var numbers = [1, 2, 3, 4, 5, 6, 7, 8, …], sum = 0, num;
				for(var i = 0; i < numbers.length; i++){
				    num = numbers[i];
				    num *= num;
				    if(num % 2 === 0){
				        sum += num;
				    }
				}
			

Переиспользуемые функции

				numbers
				    .map(square)
				    .filter(isEven)
				    .reduce(sum);
			

Чистота функции

Чистая функция (англ. pure function) — функция, которая:

Побочный эффект

Побочный эффект функции (англ. side effect) — возможность функции в процессе выполнения:

Нужные побочные эффекты

Преимущества чистых функций

Придется чем-то жертвовать

Immutable

Неизменяемые данные (англ. immutable) — данные, которые программа не может модифицировать в процессе своей работы.

Динамическое состояние — зло.

Изменяемые данные (mutable)

				var array = [];
				array.push(data);
				array; // [data]
			

Неизменяемые данные (immutable)

				var array = Immutable.List([]);
				var newArray = array.push(data);
				array; // []
				newArray; // [data]
			

ImmutableJS — библиотека с набором неизменяемых структур данных. Разрабатывается в Facebook.

Функция высшего порядка

Функция высшего порядка (англ. higher-order function) — функция, принимающая в качестве аргументов другие функции или возвращающая другую функцию в качестве результата.

Классические примеры: map, filter, reduce

Так же называют операторами (термин из математики)

Функция высшего порядка

Классический пример — функции для работы с массивом.

				map(parseInt, ['1', '123', '4'])
				filter(function(n){ return n > 100; }, [1984, 234, 1]);
				reduce(max, [...]);
			

Пример

				function property(key, target){
				    return target[key];
				}
			
property('length', [1, 2]); // 2

Как применить к списку?

				var listOfUsers = [{name: 'Ann'}, {name: 'Max'}, ...];
				
			

Частичное применение

Частичное применение (англ. partial application) — процесс фиксации части аргументов функции, который создает другую функцию с меньшим количеством аргументов.

Частичное применение

				var username = partial(property, 'name');
				username({name: 'Alex'}); // 'Alex'
				map(username, listOfUsers);
				// [{name: 'Ann'}, {name: 'Max'}] -> ['Ann', 'Max']
			

Реализация

				// VanillaJS
				fn.bind(context, arg1, arg2, ...);
			
				// UnderscoreJS
				_.partial(fn, arg1, arg2, ...);
			

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

Каррирование (англ. currying) — преобразование функции от многих аргументов в функцию, берущую свои аргументы по одному.

				var property = curry(function(key, target){
				    return target[key];
				});
			

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

				var duration = property('duration');
				// (target) => target['duration'];
				duration({ duration: 10, ... }); // 10
			
				property('name', {name: 'Sam'}); // 'Sam'
			

Реализация

Композиция

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

fn1(fn2(value));

Виды композиции

  Один Много
Синхронно Композиция Итератор
Асинхронно Promise Observable

Прежде чем писать код — выбери правильный инструмент!

Композиция

				function compose(f, g){
				
				}
			

Композиция — реальность

				function compose(f, g){
				    return function(){
				        return f(g.apply(this, arguments));
				    };
				}
			

Недостатки

Связывание

Haskell:

fn1 >>= fn2 >>= fn3

Пример

				nullsafe(data, 'company.description')
				    .bind(toLowerCase)
				    .bind(translate)
				    .bind(alert);
			

Just & Nothing

				Just(5)
				    .bind(square)
				    .bind(isEven)
				    .bind(alert);
			
				function isEven(number){
				    return number % 2 ? Nothing() : Just(number);
				}
			

Just & Nothing

				Nothing()
				    .bind(alert); // иии ничего не происходит
			

Реализация Just

				function Just(value){
				    return value && value.isMonad ? value : {
				        isMonad: true,
				        bind: function(morphism){
				            return Just(morphism(value));
				        }
				    };
				};
			

Бесплатно, без СМС: github.com/alexeyraspopov/maybe

Асинхронные операции

				getUserInfo(function(error, info){
				    if (error) throw error;
				    getUserTweets(info, function(error, tweets){
				        if (error) throw error;
				        sendToEmail(tweets);
				    });
				});
			

События

События — плохой выбор для управления потоком данных. Они требуют распространения изменяемых (mutable) данных по вашему коду, и это не идиоматически или принятно для потока данных.

Tom Ashworth

Асинхронный путь джедая

				getUserInfo()
				    .then(getUserTweets)
				    .then(sendToEmail);
			

Много, Асинхронно — FRP

Функциональное Реактивное Программирование (англ. functional reactive programming) — парадигма, ориентированная на потоки данных и распространение изменений.

Event Stream, он же Observable — источник сообщений.

Promise + Итератор

Пишем игру. Управление игроком

				document.addEventListener('keydown', function(event){
				    if(event.keyCode >= 37 && event.keyCode <= 40){
				         // Механика игры здесь
				    }
				});
			

Пишем игру. Управление игроком (bacon.js)

				$(document).asEventStream('keydown')
				    .filter(isArrow)
				    .map(selectDirection)
				    .onValue((direction) => /* Что-то происходит */)
			

Drag’n’drop на FRP, см. демо

			    var draggingDeltas = startDrag.flatMap(function() {
			        return html.asEventStream('mousemove')
			            .map(xyFromEvent)
			            .slidingWindow(2,2)
			            .map(getDelta)
			            .takeUntil(endDrag)
			    })
			

Один

				// Один, синхронно
				maybe(user.bio).bind(prettyBio).bind(output);
			
				// Один, асинхронно
				fetch('/user').then(tweetsByUser).then(filteredRetweets);
			

Много

				// Много, синхронно
				numbers
				    .filter(isEven).map(square).reduce(sum, 0);
			
				// Много, асинхронно
				event('keydown', document)
				    .filter(isArrow).map(coords).subscribe(render);
			

Что делать?

Почему?

Интересно

Спасибо за внимание!

goo.gl/tDwIpI

Powered by Shower