JS ФП тезисы
Источники
Парадигмы (стили) программирования
Языки программирования состоят из операторов, условных операторов, операторов цикла и функций. Наличие условных операторов и операторов циклов являются отличительными чертами "императивных языков программирования". Функциональные языки, как правило, поддерживают только операторы и функции.
Интересно, что ни один из трех языков, Java, C ++ и C, не являются функциональными языками программирования.Язык C - императивный язык программирования, C ++ и Java, императивный / объектно- ориентированные языки программирования. Т.е. существуют три парадигмы (стиля) программирования: императив, объектно-ориентированный и функциональный. Существует еще один, декларативный стиль.
Различия между этими парадигмами заложены в основе. Императив и объектно-ориентированное программирование основано на «машине Тьюринга». Функциональное программирование на базе "лямбда-исчисления", а декларативное программирование основано на «логике первого порядка". Будут рассмотрены различия между, императивным, объектно-ориентированным и функциональном программировании на практическом уровне.
В императивном языке программирования изменение состояния программы достигается путем выполнения серии операторов, и выполняет контроль потока, прежде всего, с помощью условных операторов, операторов цикла и вызовов функций. Программа, приведенная ниже простая реализация метода JavaScript Array.join в императивном стиле.
function simpleJoin(stringArray) { var accumulator = ''; for (var i=0, l=stringArray.length; i < l; i++) { accumulator = accumulator + stringArray[i]; } return accumulator; }
Код выше - последовательный. Мы перебераем массив и добавить каждый элемент в строку-аккумулятор и возвращаем аккумулятор. Сейчас мы перепишем эту функцию в объектно-ориентированном способом. Так как JavaScript имеет класс Array, мы добавим этот метод для класса Array, так что каждый экземпляр этого класса получит доступ к этой функции. JavaScript использует наследование через прототипы и поэтому мы добавляем эту функцию, в прототип массива.
Array.prototype.simpleJoin = function() { var accumulator = ""; for (var i=0, l=this.length; i < l; i++) { accumulator = accumulator + this[i]; } return accumulator; }
Объектно-ориентированный вариант похож на императивную версию, кроме того, что функция (метод) теперь метод класса. Объектно-ориентированные языки, как правило, императивные также.
Теперь запишем функциональную версию этой функции.
function simpleJoin(stringArray, i, accumulator) { if (i === stringArray.length) { return accumulator; } else { return simpleJoin(stringArray, i+1, accumulator+stringArray[i]) } }
Первое, что нужно отметить, это то, что мы не используем здесь цикл для итерации. Вместо этого для итерации используется рекурсия. Действительно, это одна из характеристик функционального языка программирования.
Функция называется впервые для данного массива в StringArray, i установлено в 0, и аккумулятор установлен в "". Второй раз функции вызывается из себя с той же StringArray, я установлен на i + 1, и аккумулятор установлен в аккумуляторе + StringArray [i]. И мы продолжаем так же, пока i === stringArray.length, когда мы возвращаемся аккумулятор. Мы будем обсуждать рекурсию подробно позже в более поздней почте. Просто помните, мы использовали рекурсию для этого итерации здесь.
Но осталось кое-что еще от императивного стиля - условный оператор. Functional languages tend to use expressions that evaluate to some value, instead of statements that don't evaluate to anything. Итак, давайте перепишем функцию, чтобы сделать его как функциональные, как можно в JavaScript.
function simpleJoin(stringArray, i, accumulator) {
return (i === stringArray.length) ? accumulator : simpleJoin(stringArray, i + 1, accumulator + stringArray[i])
}
Теперь это как функциональные, как вы можете получить с помощью JavaScript. Вместо того, чтобы, если заявление мы возвращаем значение вычисляется условной оператора?. Условный оператор? Займет условное выражение и возвращает значение одного из двух выражений, основанных состояние бытия истинным или ложным. Значение первого выражения возвращается, если верно и второе, если ложно.
Мы видим, что функциональная версия кратким. Действительно, одним из преимуществ функционального программирования является то, что поддается меньшей кода сделать то же самое, что приводит к лучшей читаемости и ремонтопригодность.
Однако в случае JavaScript, как сейчас вы не можете использовать рекурсию для этого итерации. Вы должны продолжать использовать императивный или объектно-ориентированного метода для итерации. Это потому, что JavaScript не (пока) поддерживает "хвост оптимизации вызова". Для правильного делать рекурсию, оптимизация хвост требуется вызов. Мы будем обсуждать хвостовую рекурсию, и хвостовая оптимизация, и как обойти эту проблему в будущем пост. Как написания этого поста хвостовая оптимизация ожидается в ECMAScript 6.
Так JavaScript императивный язык, или объектно-ориентированный язык, или функциональный язык? Это мульти язык парадигма. Это не все функциональные возможности, реализуемые. Но он медленно получение там. Это также верно для большинства других языков. Большинство языков (кроме функциональных языков, чтобы начать с) добавили функциональные возможности для различных степеней на протяжении многих лет. Хорошим примером мульти парадигмы природы JavaScript является метод Array.forEach. Вот простая реализация возможности. Обратите внимание, что все современные браузеры уже реализовали это.
М
С точки зрения программиста монада - это абстрактный контейнер с тремя функциями.
- map — заменяет содержимое контейнера без изменения самого контейнера. Заменяем каждый гвоздь в коробке шурупом, каждый int в массиве float-ом — так map и работает.
- unit — берет элемент и возвращает контейнер с одним этим элементом. Делаем из гвоздя коробку с одним гвоздем. Делаем из int массив из одного int.
- join — уменьшает вложенность контейнеров — из коробки коробок гвоздей делает коробку с гвоздями (из массива массивов int-ов — массив int-ов). Ну или из коробки коробок коробок гвоздей делаем коробку коробок гвоздей. Это уже сложная концепция, доступная только программистам и более абстрактно развитым товарищам; обычный человек будет обескуражен тем, как в одну коробку могли поместиться несколько точно таких-же коробок. Впрочем, простая замена коробок коробок на мешки мешков или пакеты пакетов позволяет совершить абстрактно-теоретико-категориальный прорыв.
Монада - это интерфейс с двумя методами:
- "поднять в монаду". Давайте называть этот метод 'pure'. Функция от одного аргумента. На входе какое-то значение, на выходе это же значение, но помеченое другим типом.
- "применить функцию к значению в монаде" или "совершить действие". В энергичном языке, думаю, уместо было бы название 'apply'. Функция от двух аргументов. На входе монадическое значение (полученное из первой функции) и собственно функция-действие, которое нужно применить к первому аргументу. На выходе новое монадическое значение, то есть изменённое функцией-действитем. Ну, эта особенность с "на выходе новое", она в общем-то нужна в языках с одним присваиванием, в остальных можно передать монадическое значение по ссылке и поменять его.
Всё остальное относится к конкретным монадам и рассматривать их нужно отдельно.
Собственно, весь смысл в двух вещах:
- новый тип даёт инкапсуляцию
- явная передача функции-действия развязывает (decoupling) их от собственно процесса применения действия. И основная фишка в том, что этот, своего рода, late binding может происходить не в рантайме (как в технологии COM, если знаете), а во время компиляции. С хорошей поддержкой полиморфизма в системе типов можно:
- гибко определять как будут выполняться одинаковые действия в разных монадах
- повторно использовать однажды определённые действия в новых монадах.