Функциональное программирование с примерами на JavaScript. Часть первая. Основные техники


Оглавление (нажмите, чтобы открыть):

Разработка на React. Функциональное программирование

Продолжая серию статей о React, поговорим о функциональной парадигме программирования, так как React и Flux основаны на функциональных методах.

Функциональное программирование одна из “горячих” тем из мира JavaScript. Но как раздел дискретной математики и парадигма программирования существует еще с давних пор. Функциональному, как правило, противопоставляется императивный подход к программированию.

Императивный подход предполагает за собой выполнение последовательных действий на протяжении программы, хранение и изменение состояния приложения.

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

Многие использовали некоторые концепции функционального программирования в императивном подходе сами того не понимая, к примеру, “анонимные функции” или “callback-функции”, которые хорошо развиты в таких языках как C#, Python и др. Также анонимные функции (они же “стрелочные функции” =>) появились в ES6 версии JavaScript, что сделало его более пригодным к функциональному подходу.

История

Истоками функционального программирования является лямбда-исчисление, которое появилось в далеких 1930-х, благодаря математику Алонзо Черчу (Alonzo Church). Которое было разработано для формализации и анализа вычислимости. Это было формальной системой до тех пор, пока в 1950-х Джон Маккарти (John McCarthy), исследователь в области искусственного интеллекта, не проявил интерес к исследованиям Алонзо Черча. В 1958 году Джон Маккарти уже представил язык программирования LISP, основанный на лямбда-логике, он и стал первым функциональным языком. Хотя с первых версий и имел наклонности к императивности.

Концепции

Функциональное программирование весьма специфичен и сильно отличается от других парадигм. Но в реальных проектах приложений часто используется их смешение. К примеру, при использовании императивного подхода нередко используются описанные ниже концепции.

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

Чистыми называются функции, которые производят вычисление на основе ее аргументов и всегда возвращают значение или другую функцию, а также не имеющие побочных эффектов.

Побочными эффектами функции являются:

  • Чтение и запись глобальных переменных;
  • Реагирование и обработка внешних событий;
  • Зависимость результатов от внешних факторов;
  • Выполнение ввода-вывода.

Библиотека контроля состояний — Redux, которая включает в себя ядро с идеей чистых функций.

Состояние

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

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

В React существует понятие Stateless Functional Component. Это компоненент который не содержит состояния (state) или ссылок на DOM объекты, в них передаются параметры props и контекст. Повторюсь, такие функции легко переиспользовать и тестировать, так как они не имеют побочных эффектов.

Приложения без состояния, на самом деле, управляют состоянием, каждый раз создавая новое состояние, без изменения предыдущего. Например, у React существует функциональный setState для изменения состояния компонентов отдельно от объявления класса, о котором чуть позже поговорим более подробнее.

Плюсы:

  • Тестируемость
  • Ссылочная прозрачность

Минусы:

  • Нагрузка на память
  • Нагрузка на CPU

Неизменяемость

Неизменяемость подразумевает под собой, что во время выполнения программа не может изменять свои данные, а лишь создавать их копии. Другими словами неизменяемый объект (immutable) нельзя изменить после его создания, и наоборот изменяемый объект (mutable) может быть изменен после создания. В js неизменяемыми являются примитивные типы данных. Примитивный тип данных хранится как значение, а объекты как ссылка. По этому при изменении примитивных типов данных всегда новое значение, без изменения старого. Изменяемые данные хороши тем, что не нужно создавать новых объектов. Но это опасно, так как при изменении этого объекта, все ссылки на него будут содержать те же самые данные.

Неизменяемость занимает ключевое место в функциональном программировании. Использование изменяемых данных чаще всего приводит к ошибкам, чем неизменяемое. В JS не стоит путать с зарезервированным словом const, оно лишь создает переменную которой нельзя переназначать после создания. Данные созданные с использованием const не являются иммутабельными, так как свойства этого объекта все еще изменяемы.

С выхода ES6 работать с неизменяемыми данными стало гораздо проще. К примеру, новый объект можно создать через Object.assign() или через оператор расширения.

Но такой синтаксис может ухудшить читаемость кода. Альтернативный вариат – использовать object spread syntax и object spread syntax, он же оператор распространения.

Плюсы:

  • Код становится более прозрачным;
  • Меньше неочевидных ошибок;
  • Проще мемоизировать;
  • Атомарность создания объектов;
  • Проще тестировать.

Минусы:

  • Нагрузка на CPU;
  • Нагрузка на память и сборщик мусора.

Функции высших порядков

Функции высших порядков — это такие функции которых один из аргументов имеется функция и/или возвращает функцию. Ярким примером в JavaScript являются встроенные функции map , filter , join и т.д

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

Каррирование имеет прямое отношение к функциям высших порядков. Это способ конструирования функций, где функция от n аргументов превращается в цепочку вложенных n функций.

Композиция

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

Так как при функциональном подходе логика программы разбивается на небольшие чистые функции, то для работы приложения необходимо их собрать в более крупные функции. Этот процесс называется композицией. При составлении композиции используется много различных подходов. Один из них, вероятно вам уже знакомый, композиция через вложение функций в аргументы другой функции

Но такой способ имеет существенный недостаток – он плохо масштабируем. Есть более гибкое решение, композиция через compose . У этого метода, также существует множество реализаций. Приведу пример, с реализацией через call .

Такой подход более масштабируем и более подходит для длинных цепочек. Либо есть вариант вызова compose с полным списком функций.

На вход compose получает значение, которое будет передано в качестве аргумента для первой функции, которая в свою очередь передает результат следующей, в конечном счете compose возвращает единственное значение, результат работы всех функций.

Рекурсия

В функциональной парадигме не существует такого понятия как цикл. Поэтому перебор данных производится посредством рекурсии. Рекурсивные функции вызывают сами себя.

Классическим примером является вычисление факториала.

Сначала вычислим факториал посредством циклов:

И то же самое, только используя рекурсивные вызовы:

Плюсы

  • Читабельный код
  • Простая отладка
  • Неизменяемость и однозначность переменных

Минусы

  • Расточительность
  • Меньшая скорость

Резюме

Так какие, в итоге, имеем преимущества ФП:

  • Иммутабельность позволяет соблюдать персистентность, проводить безопасные операции с данными.
  • Переиспользование избавляет от повторяющегося кода и делает его более прозрачным.
  • Чистота функций улучшает тестируемость и прозрачность кода.
  • Краткость функционального кода, который легко поддерживать в больших проектах.

Функциональное программирование в отличие от ООП сложнее в изучении, так как оно не отображает объекты реального мира как ООП. Но преимущества ФП того стоят. Тем более, никто не запрещает использовать функциональную парадигму в связке с другими.

Это был краткий обзор основных принципов и преимуществ функционального программирования, которые будут применяться в примерах дальнейших статей о React и Flux.

Информационный портал по безопасности

Функциональный JavaScript, Часть 1: Введение

Автор: admin от 15-07-2014, 08:10, посмотрело: 1022

Введение

javascript мощный, но недопонятый язык программирования. Людям нравится говорить, что он является объектно-ориентированным языком программирования или является функциональным языком. Другим нравится говорить, что он не является объектно ориентированным или не является функциональным языком программирования. Некоторые скажут что он относится к обоим парадигмам или ни к одной из них — но, давайте отложим на пока этот спор в сторонку.

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

Функциональное программирование (сильно) недопонято в JS-мире

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

Я думаю, что это результат того, что большинство языков программирования используемых для веб-разработки на стороне сервера, уходят корнями в Си, и большинство согласится, что он не функциональный язык программирования.

Тут существует два момента. Первый момент может быть продемонстрирован следующим примером на jQuery, который является широко используемым:

Эй, посмотрите-ка на это. Мы только что передали анонимную функцию как аргумент, также известным в javascript под гадким названием как callback-функция.
Некоторые называют это функциональным программированием. Так ли это? Не совсем!

Этот пример является демонстрацией ключевой фишки функционального программирования: функция как параметр. С другой стороны этот пример также идёт вразрез почти с любой другой из парадигм функционального программирования с какими вообще можно пойти вразрез в трёх строчках примера.
Второй момент чуточку более коварный. Читая это, некоторые модные JS разработчики думают про себя:

Underscore.js — довольно популярная javascript библиотека, используемая повсеместно. Для примера, скажем, что у меня имеется набор слов и нам необходим соответствующий набор из первых двух букв каждого слова. Вместе с Underscore.js это довольно легкая задача:

Хотя подчеркивание и функции вроде _.map — полезные функциональные парадигмы, однако способ их совместного использования в этом примере выглядит… многословным и тяжело воспринимается мной. Неужели нам все это нужно?

Если мы начнем думать о вещах чуточку более «функционально», то, наверное, взяв пример выше, мы получим это:

Если вы подумаете об этом, то увидите всю ту же информацию в одной строчке, что и в пяти строках выше. words и word просто параметры/заполнители. Реальное мясо логики заключается в комбинировании функции map, функции first и константы 2 в осмысленном виде.

Некоторые, глядя на этот пример, могут задуматься: что ещё за «капелька магии». В конце концов, вставлять любой пример с «капелькой магии» как в примере это как… своего рода обман, а?

Что ж, я собираюсь посвятить парочку следующих постов, объясняя эту «капельку магии», так что если заинтригованы — будьте добры продолжать.

Эта серия постов призвана помочь другим научиться заимствовать красоту из языков функционального программирования в контексте javascript.

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

Читать далее -> Часть 2: что делает язык «функциональным»?

Об ошибках и неточностях просьба писать сюда

Javascript и функциональное программирование: полноправные функции

Примечание. Данное описание является второй частью серии «Javascript и функциональное программирование» по изучению методов функционального программирования в JavaScript ES6+. Чтобы начать снуля, см. Часть 1

Добро пожаловать в параллельный мир

Вам необходимо кое-что узнать прежде, чем мы начнем… Если вы когда-либо программировали в JS, вы, вероятно, раньше использовали шаблоны FP! Данные шаблоны и подходы присутствовали всегда: мы просто не могли их правильно рассмотреть. Мы начнем с уже известного, а затем перейдем к исследованию новой территории. Кое-что может быть немного, скажем, странно. Но не бойтесь! Вместе мы справимся!

Полноправные функции

В Javascript функции являются объектами первого уровня. Как я уже упоминал ранее, нам не нравится загадочная терминология, поэтому давайте разъясним. Согласно глоссарию разработчиков Mozilla:

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

Функции как самозначимые группы символов

В следующем примере мы объявим самозначимую группу символов (const) и назначим ей анонимные функции стрелок.

После первоначального присваивания constFunction является самозначимой группой символов со значением функции. Мы проверяем это путем регистрации переменной constFunction в инструменте исследования Chrome. Поскольку constFunction ‒ это функция, которую мы также можем вызвать.

Функции как значения ключей объекта

Теперь, когда мы понимаем, что переменные могут содержать функции, давайте продемонстрируем функцию как значение ключа в объекте. Это должно быть знакомо тем, кто раньше занимался объектно-ориентированным программированием.

Функции как элементы массива

Когда функции являются объектами первого уровня, мы можем передавать их в качестве данных в массив, как и любой другой типданных. Давайте используем консоль Chrome и проверим это.

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

Теперь, когда мы разогрелись, давайте займемся интересным �� Разработчики JS видят функции, которые ежедневно принимают другиефункции в качестве аргументов. Если вы переходите с языка, который не поддерживает FP, это должно показаться немного странным �������������� Давайте ознакомимся с данной концепцией, рассмотрев некоторые примеры.

Асинхронная функция, которая принимаетфункцию обратного вызова

В данном примере мы используем модуль jsonfile npm для метода writeFile. Третий параметр, который ожидается writeFile, является функцией. Когда метод jsonfile.writeFile выполняет его, он либо будет успешным, либо завершится ошибкой. В противном случае он выполнит функцию errorLoggerFunction. В качестве альтернативы мы могли бы использовать более краткую синтаксическую структуру и посмотреть указанную функцию:

setTimeout

В данном примере показан встроенный асинхронный метод setTimeout, который принимает 2 аргумента. Давайте немного упростим это и объясним функцию setTimeout в терминах функционального программирования.

Начнем с чтения подписи функции. Мы можем заметить, что количество аргументов, которые принимает setTimeout, равно двум. В функциональном программировании число аргументов, которыепринимает функция, называется «арность», произошедшее от таких слов, как унарный, двоичный, тернарный и т.д. Таким образом, мы можем сказать, что setTimeout имеет арность 2 или можно сказать, что она имеет двоичную (бинарную) функцию.

Аргументами, которые ожидаются setTimeout, является функция и временной интервал ожидания перед выполнением данной функции. Хм … Другая функция, которая принимает функцию в качестве входа?

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

Функция высшего порядка – это функция, которая принимает функцию в качестве аргумента или возвращает функцию.

Итак. Теперь вы можете незаметно упомянуть это в любом случайном разговоре на работе / с друзьями и выглядеть, как босс! ? ������

Давайте немного повеселимся и создадим массив (список) функций в следующем примере.

В строке 5 мы объявляем массив функций. Затем мы используем метод forEach для итерации по массиву. forEach ‒ это поддерживаемая пользователем функция ES6+, которая принимает функцию, выполняемую для каждого элемента массива. Следовательно, forEach также являетсяфункцией высшего порядка!

Наш forEach принимает анонимную функцию в качестве входных данных. forEach будет перебирать массив, неявно обращаться к текущему элементу массива и называть его getCalculation. Стоит отметить, что forEach неявно обращается к элементам массива по сравнению с тем, как мы могли бы получить доступ к текущему элементу, если бы мы использовали регулярный цикл с параметром, то есть, arrayOfFunctions [i]. Каждый элемент в нашем массиве является функцией, поэтому мы вызываем getCalculation с аргументами, которые она ожидает.

Фантастика. Данный пример иллюстрирует, что функции вфункциональном программировании могут быть переданы в массивы (списки), как и любой другой тип данных. Функции могут быть переданы куда угодно!

Теперь давайте построим собственную функцию более высокого порядка!

Функция addWrapper возвращает простую функцию добавления при вызове. Вызывая результат функции addWrapper и предоставляя ему два аргумента, мы имеем доступ к функции анонимного добавления.

Мы могли бы пойти еще дальше и написать такую функцию, которая возвращает функцию, которая, в свою очередь, возвращает свою собственную функцию!

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

Полноправные функции являются краеугольным камнем любогофункционального языка программирования. Главное, что вы должны вынести из нашего обсуждения полноправных функций, это то, что функции могут быть назначены как самозначимые группы символов, переменные, помещены как элементы массива и даже заданы как значения ключей для объекта. Кроме того, (и, самое главное?!) функции могут быть возвращены в функции и из функций, как и любой другой типданных!

Функциональный JavaScript

Осторожно! Слабонервным, холерикам и беременным не читать — выносит мозг, меняет сознание!

Переменные

Переменные — зло. Основная причина — это то, что они переменные т.е. меняют своё состояние во времени самым непредсказуемым образом. Проблемы начинаются когда переменных появляется много то за ними трудно уследить, а так же количество кода, логика которого зависит он них, и к статиб автор переменной никогда не может быть уверен как ее используют в будущем. Другими словами, закон Мерфи(«Что может произойти то обязательно произойдет») в действии, любой кусок кода который имеет доступ к переменной может изменить самым непредсказуемым образом.

С появлением переменной появляется зависимость во времени до и после присваивания. Проблема в том что переменные приносят в систему понятие состояния. Как правило каждое состояние приносит нам по два метода для роботы с ним, классический пример, malloc и free , open и close , getCanvas и freeCanvas и т.д.

Каждую из перечисленных функций надо вызвать в правильном порядке и при том, если не вызвать функцию освобождения, то появляются проблемы часто сложно уловимые. Поэтому в мире столько ужастиков с утечкой памяти и множество методов борьбы с ним. С выделением памяти целая эпопея, поэтому люди придумали сборщики мусора которые решают созданную проблему вместо программистов, местами не очень хорошо , привет stop world . Но вот проблема, сборщика мусора нет для файлов, нет для холста. для многих вещей нет.

Мастер Йода рекомендует:  Реализация препроцессора для C на Python

Пример с сменой состояния:

Во-первых, в примере видно плохие имена переменных �� во-вторых, видно, как функция обращается к переменной s и меняет её состояние. Так что будет, если переменная s будет иметь значение 2? Да, верно программа «вылетит» с исключением, но только в текущем event loop. Следующий код, который будет читать значения s и q , получит значения в не консистентном состоянии, т.е. другие части программы могут не ожидать таких значений и могут работать неверно.

Поэтому желательно не использовать переменные или использовать их в наименьшей области видимости, насколько это возможно — чем меньше кода имеет доступ к переменной, тем лучше. В идеале время жизни переменной должно быть в пределах 5-15 линий кода. Время жизни переменной это «путь» от её создания до последнего использования. Но так как эта запись о функциях, то вернёмся к ним.

Объявление функций

Функции – это хлеб с маслом функционального программирования, и JavaScript частично поддерживает функциональный подход. Идея оборачивания куска программы и вызова её как переменной очень востребована. Это инструмент для структурирования больших программ, уменьшения повторений, назначения имён подпрограммам, и изолирование подпрограмм друг от друга.

Объявление функции сродное объявлению обычной переменной:

Здесь переменной square присваивается функция, которую можно вызвать в любом другом месте, передать ей параметр x и она вернёт квадрат этого значения. Прошу обратить внимание на то, что функция не меняет состояние никаких внешних переменных (чистая функция, без побочных эффектов), а также является детерминированной, т.е. для одинаковых входных данных будет возвращать одинаковый результат. Ещё один вариант объявления:

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

Вызов функций

Имея функцию, сохранённую в переменной или просто определенную в текущей или доступной области видимости, её можно использовать собственно для чего она и предназначалась, иными словами вызвать её с необходимыми параметрами и получить ожидаемый результат. Есть несколько способов, как это можно сделать. Первый метод собственно классический: используя скобки funcName(x, y) , где funcName имя функции а x и y передаваемые параметры (аргументы). Собственно результат своей работы функция возвращает «левее», и если его не присвоить переменной или сразу передать как аргумент другой функции, то он будет утрачен.

В JavaScript функция является по сути объектом и имеет некоторые дополнительные методы. Первый из них это метод call .

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

Следующий метод apply очень похож на первый но с тем отличием что второй параметр должен содержать массив аргументов которые будут переданы в вызываемую функцию.

Есть ещё один метод, но он не совсем связан з вызовом функций, скорее с созданием новой функции. Именуется этот метод bind и он также позволяет «мертво» привязать контекст и параметры к функции. Если указаны параметры — они будут прибавлены к каждому вызову новой функции, причём встанут перед теми, которые указаны при вызове. Например:

В даном примере видно как при помощи метода bind была создана новая функция, привязанная к null объекту и также продемонстрировано, как можно сделать частичную передачу параметров. Т.е. привязали аргумент 1 к функции.

Еще один довольно часто используемый фокус, когда функция сразу же после обьявления исполняется. Это позволяет скрыть переменные и логику которые не нужны в других частях программы, а также создавать объекты, в которые имеют собственные «приватные» переменные/методы:

Контекст

В языке Си есть такое понятие как структуры ( struct ) они удобны тем, что позволяют из простых типов создать сложную структуру с полями, через которые можно обратится к значению.

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

К тому же, всегда первым параметром выступала структура (точнее указатель на неё), которую нужно изменить. И тогда изобрели «классы». В общем, добавили возможность в структуру добавлять и функции (методы), при этом при вызове функции ей автоматически передавался указатель на текущий экземпляр и стали именовать его this .

Классы имеют и много других плюшек, но они нам теперь не интересны. Какое это имеет отношение к JavaScript? Очень даже прямое. Авторы JavaScript тоже решили так сделать, только вот в JavaScript нет классов (традиционных), зато здесь есть объекты. Вот авторы и решили, что каждая функция всегда имеет «переменную» this , которая указывает на текущий объект. Если объекта нет, то указывает на глобальный (в браузерах window ), что иногда приводит к немного печальным последствиям (поэтому в новых версиях поведение немного изменили , привет use strict ). Вот как это выглядит в JavaScript:

Вызов функции которая определена в классе, делается примерно так же как и обращение к значению поля. Если очень по-простому то контекст функции будет равным тому, что идёт до точки перед именем функции. А теперь фокус:

Как поведёт себя функция скопированная у другого объекта? Верно! Изменится состояние secondObj.name , так как контекст функции будет значение secondObj . Иногда есть надобность передать функцию как аргумент в другую функцию.

Пример работать не будет. Проблема в том, что передаётся функция как переменная, и у неё нет контекста (нет точки при вызове), точнее есть глобальный, но это не то что нужно. Решение проблемы описано высшее, а именно метод bind . Для того чтоб заработало нужно сделать так:

Или же есть возможность вообще создать функцию и потом применять её в контексте любого объекта. Здесь, кстати, метод bind исполняется в контексте функции setName поэтому он знает к которой функции привязывать контекст.

В примере так же продемонстрировано, что результат, который возвращает функция, можно использовать сразу. Т.е. в данном примере метод bind возвращает новую функцию, которая сразу же вызывается. Аналогично можно делать с объектами, если функция возвращает объект можно сразу его использовать, как собственно сделано в jQuery. Данный подход называется цепочка вызовов (chaining) и бывает очень полезен, но в функциональном подходе его заменяет композиция.

Каррирование и частичное применение

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

Что делать, если нам часто надо суммировать одно число к другому, и при том одно число всегда одно и то же? Нужно где то запомнить это значение. Один из вариантов это использовать замыкание и сделать так:

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

В заголовке упомянуты два термина: карирование и частичное применение, они немного похожи, но все же отличаются. Частичное применение это когда у нас есть функция, которая ожидает несколько параметров, но первые параметры нам известны и мы их применяем, например с помощью метода bind :

Теперь при вызове функции sum3 и передачи ей остаточных параметров будет выполнено суммирование. Здесь таится отличие от каррирования если вызвать sumBase3 с одним аргументом, то суммирование будет выполнено с ошибкой, по той причине, что последний аргумент не был передан. В случае с карированием такого не произойдёт. Карированая функция так же ожидает всех аргументов, только вот суммирование не будет вызвано до тех пор, пока все аргументы не будут получены:

Вот таким вон нехитрым способом можно уменьшить количество параметров в функциях.

Сигнатура функций

В то время, как JavaScript является языком с динамической типизацией, нет необходимости указывать тип переменной. Это позволяет писать более абстрактный код, хотя временами трудно понять, что ожидает функция и что возвращает. Иногда это приводит к «неожиданным» результатам. Для того, чтобы помочь программисту быстрее ориентироваться в функциях есть несколько подходов, как описать сигнатуры функций. Ах, да, сигнатура функций это как бы описание того что функция получает (аргументы) и то что возвращает.

Первый и самый не интересный нам подход, который используется в ActionScript (часть семейства ECMAScript). В нем используется типизация и параметры описываются более явно, пример:

Плюс такого подхода в том, что транслятор может проверять типы и «бить в колокол», если что то не так, но с другой стороны у нас уменьшается абстракция, и мы должны писать ещё одну функцию в случае если немного нужно изменить сигнатуру. в общем, не гибко.

Следующий, более JavaScript подход, представляет JSDoc. Подход более лучший, так как позволят не только описать сигнатуру функций, но и дополнительную информацию, на основании которой можно сгенерировать документацию. Более того, некоторые IDE умеют читать этот формат и использовать для подсказок при разработке, что довольно удобно. Основной минус так это то, что он довольно многословен, и иногда читать смесь из документации и кода не очень-то и удобно. Пример:

И последний вариант, на котором и остановимся. Предназначен он для описания только самых сигнатур и не более. Не многословен, и нет поддержки в IDE (пока нет). Начнём с того, что он «украден» в правильных функциональных языков где каррирование у всех функций по умолчанию. Поэтому далее будем рассматривать все функции как каррированые. Пример сигнатуры:

Как видно с примера, функция работает с четырьмя параметрами первые три это собственно входные параметры, а четвёртый это результат работы. Ах, да важное замечание: в функциональных языках функции всегда возвращают что-нибудь. Если функция ничего не возвращает, то по факту она ничего не делает. и да, не забываем что в идеале функции чистые, т.е. ничего не изменяют (DOM, ввод-вывод, AJAX и т.п.). Функции также не должны изменять значения, которые переданы как аргументы. Если все же есть необходимость их изменить, тогда нужно создать копию оригинального значения и изменить его. Да, это немного расточительно по отошению к памяти, но более безопасно. Ещё пример:

Более сложный пример:

Думаю, этот случай надо более детально рассмотреть. Типов a и b не существует в природе JavaScript, это просто мнемоническое обозначение что тип может быть любой. Квадратные скобки вокруг означают, что это массив элементов a . Так же с последнего примера видно (a -> b) , что первый аргумент функции есть функция в которую будет передан один элемент с массива [a] , и она должна вернуть значение b , которое впоследствии будет помещено в массив элементов [b] . Несколько примеров для самостоятельного изучения:

Композиция

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

Нечто похожее существует уже довольно давно в *nix системах и называется конвеером (pipe).

Смысл здесь в том, что для достижения конечного результата используется несколько маленьких утилит, которые делают маленький, но необходимый кусок работы. Как в этом примере, одна утилита читает текстовый файл cat file.txt и выводит на экран, но вывод на экран перехвачен и направлен на вход другой утилите grep ^boo , которая в свою очередь фильтрует полученные данные и опять выводит на экран, где они которые опять перехватываются и передаются на вход утилите foo , которая делает с ними тоже что-то светлое и хорошее, и после снова выведет на экран но на этот раз уже успешно, так как вывод не перенаправлен, поэтому пользователь видит результат работы.

Аналогично работает функция compose : переданные ей функции она сохраняет в массиве и возвращает новую функцию, и при её вызове будет выполняться последняя функция из переданных в compose и ей же будут переданные аргументы. В свою очередь результат работы последней функции будет передан предпоследней. И так до самой первой функции, результат которой будет возвращён. С помощью такого подхода можно необходимую логику декомпозировать (розбить на более мелкие части), в результате у нас будет увеличено переиспользование кода, а значит багам будет сложнее скрыться и разработка буте идти быстрее.

Очень важно отметить, что композиция/декомпозиция способствует более абстрактному коду. Абстракция в свою очередь способствует быстрой разработке, (но!) увеличивает время на обучение. Другими словами менее опытные разработчики будут тратить больше времени на обучение.

Функтор

Для всех, кто работет з JavaScript продолжительное время, хорошо знаком метод map для массивов. Если же немного подзабыли, то напомню: он обходит весь массив и к каждому элементу применяет функцию, переданную первым аргументом, создает новый массив и в него помещает элементы/значения, которые возвращает функция. Так вот, этот метод — функтор.

Если взглянуть на него с немного большей высоты, то функтор это метод который знает внутренне устройство объекта (в нашем случае это Array ), умеет извлечь оттуда каждый элемент, передать в обрабатывающую функцию и обработанные элементы поместить обратно в объект такого же типа. Это можно ипользовать, например, для DOM или структуры дерева. Определить метод map , который обходит дерево, применяет функцию для каждого элемента и снова строит аналогичное дерево с возвращенных значений.

И вот последний еще более приземленный пример:

Построитель вычислений aka «Монады»

Hey Underscore, You’re Doing It Wrong!

Очень интересный доклад, рекомендую посмотреть, хотя если нет времени и/или желания смотреть 40 минутный доклад, ниже будет краткое содержание. Так в чем же проблема самого Underscore? Underscore позиционируется как библиотека с функциональным подходом. но проблема здесь кроется в параметрах, точнее их последовательности. Выше была описана основа функционального подхода — каррирование и композиция, так вот в Underscore сложно делать композицию через последовательность аргументов. Потому что в нем при вызове метода надо указать первым аргументом данные которые нужно обработать и после функцию которая будет обрабатывать. Это исключает возможность использовать каррирование для создания необходимых функций на лету.

В этом и кроется проблема. Здесь нам нужна переменная для сохранения данных, которые будут обработаны дальше. Что если мы сделаем функцию следующего вида:

Первое что нужно отметить мы сделали её каррированой и второе поменяли местами аргументы. И если теперь сделаем так:

У нас будет функция, которой можно передать любой массив (или не очень массив а все у чего есть метод filter ) и она вернёт фильтрованный. Думаю, её мощь пока не очень видна — тогда немного усложним.

Небольшое напоминание, что функции, скормленные compose , будут исполнены в обратном порядке. Иногда в библиотеках для упрощения делают функцию pipe , в которой аргументы-функции идут в прямом порядке.

Как видно из примера, у нас нигде явно нет передаваемых переменных с данными, но они передаются от одной функции к другой, и результат уже выводиться пользователю. Таким способом можно сделать много работы уже проверенными функциями, которые чётко делают свою роботу и без побочных эффектов (вывод пользователю это побочный эффект, но мы пока закроем на это глаза). Как помним, showResult каррированая и ей можно скормить аргументы по один, и когда она уже будет «сытая» — выдаст результат.

Более того с таким подходом можно делать и асинхронные операции:

Промисы? Колбэки? Асинхронный код? Не, не слышал. Но вот тут есть небольшая проблема: а что если сервер недоступен? Или данные должен был ввести пользователь, а их нет? Ой, беда.

Maybe

Хотя если подумать не такая уже и беда, все можно решить в функциональном стиле. Для этого в нас есть (будут?) монады! Посмотрим, как можно определить монаду maybe:

Это сильно упрощений вариант, но идею передаёт очень хорошо. Смысл в том, что данные мы не будем передавать самые по себе, а будем их оборачивать в контейнер, в котором определён метод map . И при использовании этого метода сначала проверить, есть ли собственно, данные, и в случае если их нет, то просто не вызывать функцию, и код у нас становиться:

Ой, сколько же в нас появилось map ов, для чего же их столько нужно? Ну что ж, попробуем разобраться. Все дело в том, что maybe возвращает контейнер с данными (в даном случае пускай будет будет массив), и чтоб их извлечь, мы используем метод map . Извлечённые данные фильтруем. Далее, после извлечения и фильтрации, данные опять запаковываются, и опять получаем монаду, поэтому перед обработкой данных опять извлекаем данные из монады, а после и из массива. Обрабатываем, результат опять запаковывается дважды (в массив, и в монаду/контейнер). И последняя команда: собственно перед выводом массива извлекаем из монады и передаём обработчику.

Представим случай, что данные мы не получили, т.е. в maybe ничего не передали, при каждом map для монады просто будет проверка, есть ли данные (а их нет), поэтому обработчики ( filterOdd , map(processData) . ) и не будут выполнены.

Either

Хорошо, теперь у нас есть проверка на пустые значения maybe , но что если в нескольких местах эти значения могут отсутствовать? Как найти место где нет данных? На помощь приходит мистер Propper монада Either . Её смысл очень похож на монаду Maybe , только с тем различием, что мы можем вернуть осмысленный текст ошибки. Так же эта монада удобна в функциях, где может быть исключительная ситуация, и вместо выкидывания ошибки можно просто вернуть монаду без значения, но с информацией про ошибку, что поможет локализировать проблему и не дать коду что ни будь нам сломать.

Напишем короткую функцию для демонстрации:

Чтобы избежать повторного написания везде map , может быть создана функция mcompose , которая автоматически для всех функций извлекает данные, т.е. дописывает map .

Мастер Йода рекомендует:  Серверные включения SSI (часть 1)

Обращаю внимание на то, что теперь метод getElement вынесен из композиции, так как ему на вход нужно «чистое» значение, а не монада, и возвращает он монаду.

Даже если не использовать функциональный подход, то использовать either или maybe в разработке может поднять стабильность на новый уровань. Всегда возаращая манады в качестве результата можно убрать кучу условий которые проверяют есть ли резульат от функции, а также можно забыть про try catch (которым часто злоупотребляют и оборачивают большие куски кода, и не обрабатывают пойманое исключение). Такой себе NullObject паттерн. Для удобства монадам можно добавить методы для получения данных, например, getOrElse , где единственным аргументом будет занчение по умолчанию, на случай если монада «пустая». Или метод getOr , где аргументом будет функция которая будет исполнена в случае отсутствия данных в монаде и результат в виде результата из этой функции.

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

В первом случае основные принципы понятны: вы оперируете математической логикой для вывода новых фактов и состояний из уже известных. Ярким примером такого языка является Prolog.

Принцип работы с императивным программированием, наиболее распространённым, заключается в формировании инструкций, последовательных команд, которые должна выполнять машина. За примерами далеко ходить не надо, просто откройте список самых популярных языков программирования: те, что сверху — императивные.

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

Что это

Итак, как мы уже выяснили, императивное программирование работает со строго определёнными состояниями и инструкциями. Функциональное же основывается на взаимодействии с функциями, то есть некими процессами, описывающими связь между входными и выходными параметрами. Таким образом, в то время, как императивный язык описывает конкретное действие с известными входными параметрами, функциональный описывает некое тело взаимодействий, не опускаясь до конкретных случаев.

Функциональное программирование, несмотря на кажущуюся сложность, несёт в себе ряд преимуществ:

  1. Код становится короче;
  2. Понятнее;
  3. Включает в себя признаки хороших императивных языков: модульность, типизация, чистота кода.

Примерами функциональных языков являются LISP (Clojure), Haskell, Scala, R. В общем-то, вы даже можете попробовать писать функциональный код на Python или Ruby, но это больше развлечение для мозгов, нежели рациональное использование возможностей языка.

Конкретнее

Логично, что по функциональному программированию, существующему уже почти 50 лет, написано множество книг и статей. Поэтому какой смысл представлять собственную версию «ФП для чайников», если всё уже в прекрасном и удобочитаемом виде давно есть в сети? Поэтому просто поделимся ссылками:

  1. Прекрасная статья, имеющая исторический экскурс, яркие образы, но главное хорошие примеры. Имеется перевод.
  2. Книга, которую необходимо прочитать каждому функциональщику, если можно так выразиться. Тоже есть на русском.
  3. Онлайн-курс, который можно прослушать на английском языке. Будем надеяться, что-то похожее скоро появится и у нас на GeekBrains.
  4. Забавное и познавательное слад-шоу на тему функционального программирования.
  5. Прекрасная книга про Haskell, написанная доступным языком (русским), для тех, кто созрел для полноценного изучения первого функционального языка. Справочник прилагается.
  6. Для тех, кто предпочитает начать изучение не с простого, а с хронологического начала – перевод книги Кристиана Кеннека «Les Langages Lisp». Она же «Lisp in Small Pieces».

Куда с этими знаниями идти

Что касается области применения, то функциональное программирование является незаменимым инструментом при создании искусственного интеллекта или в тех областях, где императивные языки потребляют слишком много ресурсов (например, в Data Science). Так что если решили направить свою дальнейшую карьеру в это русло, то самое время обложиться описанной выше литературой и оставить свой след в чьей-то виртуальной голове.

Если вы новичок в мире программирования, то возможно ещё не знаете, что существуют три основных парадигмы: логическое программирование, императивное и функциональное.

В первом случае основные принципы понятны: вы оперируете математической логикой для вывода новых фактов и состояний из уже известных. Ярким примером такого языка является Prolog.

Принцип работы с императивным программированием, наиболее распространённым, заключается в формировании инструкций, последовательных команд, которые должна выполнять машина. За примерами далеко ходить не надо, просто откройте список самых популярных языков программирования: те, что сверху — императивные.

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

Что это

Итак, как мы уже выяснили, императивное программирование работает со строго определёнными состояниями и инструкциями. Функциональное же основывается на взаимодействии с функциями, то есть некими процессами, описывающими связь между входными и выходными параметрами. Таким образом, в то время, как императивный язык описывает конкретное действие с известными входными параметрами, функциональный описывает некое тело взаимодействий, не опускаясь до конкретных случаев.

Функциональное программирование, несмотря на кажущуюся сложность, несёт в себе ряд преимуществ:

  1. Код становится короче;
  2. Понятнее;
  3. Включает в себя признаки хороших императивных языков: модульность, типизация, чистота кода.

Примерами функциональных языков являются LISP (Clojure), Haskell, Scala, R. В общем-то, вы даже можете попробовать писать функциональный код на Python или Ruby, но это больше развлечение для мозгов, нежели рациональное использование возможностей языка.

Конкретнее

Логично, что по функциональному программированию, существующему уже почти 50 лет, написано множество книг и статей. Поэтому какой смысл представлять собственную версию «ФП для чайников», если всё уже в прекрасном и удобочитаемом виде давно есть в сети? Поэтому просто поделимся ссылками:

  1. Прекрасная статья, имеющая исторический экскурс, яркие образы, но главное хорошие примеры. Имеется перевод.
  2. Книга, которую необходимо прочитать каждому функциональщику, если можно так выразиться. Тоже есть на русском.
  3. Онлайн-курс, который можно прослушать на английском языке. Будем надеяться, что-то похожее скоро появится и у нас на GeekBrains.
  4. Забавное и познавательное слад-шоу на тему функционального программирования.
  5. Прекрасная книга про Haskell, написанная доступным языком (русским), для тех, кто созрел для полноценного изучения первого функционального языка. Справочник прилагается.
  6. Для тех, кто предпочитает начать изучение не с простого, а с хронологического начала – перевод книги Кристиана Кеннека «Les Langages Lisp». Она же «Lisp in Small Pieces».

Куда с этими знаниями идти

Что касается области применения, то функциональное программирование является незаменимым инструментом при создании искусственного интеллекта или в тех областях, где императивные языки потребляют слишком много ресурсов (например, в Data Science). Так что если решили направить свою дальнейшую карьеру в это русло, то самое время обложиться описанной выше литературой и оставить свой след в чьей-то виртуальной голове.

Введение в функциональное программирование на JavaScript

Вы наверно слышали о том, что JavaScript – функциональный язык, или хотя бы о том, что он поддерживает функциональное программирование. Но что же такое функциональное программирование? И если вы решили сравнить парадигмы программирования, то чем же отличается функциональный подход от того самого JavaScript, на котором вы обычно пишете код?

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

Функциональный JavaScript не нужно применять во всем проекте, чтобы получить пользу. Совсем немного знаний о функциональном подходе, поможет вам принимать решения, в процессе проектирования проектов, независимо от того, как вы предпочитаете структурировать свой код. Знание нескольких функциональных паттернов и методов, может подтолкнуть вас к тому, чтобы вы писали код на JavaScript чище и элегантнее, независимо от того, как подход вы предпочитаете.

Императивный JavaScript

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

Хотя разработчики изо всех сил старались сопоставить гибкость JavaScript со сложностью объектной модели документа браузера(DOM), фактический код JavaScript часто выглядел чем-то вроде:

Так много вещей происходят в этом маленьком фрагменте кода. Переменные определяются на глобальном масштабе. Значения раздаются и модифицируются функциями. Сегодня методы DOM смешиваются с нативным JavaScript. Имена функций не очень дескриптивные, и это отчасти упирается в то, что многие вещи зависят от контекста, который может существовать, а может и нет. Но если вы запустите этот код в браузере, в HTML документе, который определяет вы скорее всего получите запрос на текст, который можно редактировать, а затем увидите, что указанный запрос, содержит в себе слова, каждое из которых начинается с заглавной буквы.

Императивный код, так же как этот запрос, написан, чтобы быть прочитанным и выполняется сверху вниз (плюс-минус немного поднятия переменных). Однако есть несколько улучшений, которые мы можем внести, чтобы сделать его более читабельным и чистым, всего лишь воспользовавшись объектно-ориентированной природой JavaScript.

Объектно-ориентированный JavaScript

Спустя несколько лет, разработчики начали замечать проблемы с императивным программированием в общей среде, например в браузере. Глобальные переменные из одного фрагмента кода на JavaScript могли нарушить глобальные переменные, установленные другим фрагементом кода. Порядок, в котором код воздействовал на результы, которые могут быть непредсказуемыми, особенно с учетом задержек сетевого соединения и временем исполнения.

В конце концов, возникли несколько хороших приемов для того чтобы помочь инкапсуляции кода JavaScript и лучше согласовать его с DOM. Обновленный вариант того же кода, что и выше, написанного в соответствии с объектно-ориентированным стандартом, может выглядеть следующим образом:

В этой объектно-ориентированной версии, функция конструктора имитирует класс для моделирования объекта, который нам нужен. Методы включены в прототип нового объекта, для того, чтобы сохранить минимальное использование памяти. И весь код изолирован в анонимной функции-выражении, вызываемой сразу после создания, чтобы не засорять глобальную область действия идентификатора. Там даже есть директива строгого использования, для того, чтобы можно было воспользоваться последним движком JavaScript, а старомодный метод OnClick заменен новым addEventListener, потому что, кто сегодня в здравом уме использует версию IE8 или более ранние версии? Скрипт, подобный этому, вероятно будет вставлен в конце элемента в HTML-документе, чтобы убедиться, что вся DOM загружена, прежде чем указанный скрипт будет обработан, так что , к которой он относится, будет доступна.

Но, несмотря на все реконфигурации, остается много рабочих продуктов императивного стиля, которые привели нас сюда. Методы в функции конструктора зависят от переменных, область видимости которых распространяется вплоть до родительского объекта. Там есть циклические конструкции для перебора элементов массива, состоящего из строк. Так же есть переменная counter, которая служит единственной цели, а именно увеличению прохождения цикла for. И есть методы, которые дают побочный эффект модификации переменных,существующих за пределами своих собственных определений. Все это делает код хрупким, менее мобильным, и создает определенные сложности для тестирования методов вне данного узкого контекста.

Функциональный JavaScript

Объектно-ориентированный подход намного чище и более модульный, чем императивный подход, с которого мы начали, но давайте посмотрим, можем ли мы улучшить его, решив некоторые проблемы, которые обсуждались выше. Было бы здорово, если бы мы смогли найти способы воспользоваться встроенными возможностями JavaScript обрабатывать функции как объекты первого уровня, и таким образом наш код станет чище, более стабильным, и его можно будет легче перепрофилировать.

Вы заметили, насколько короче данная версия? Мы задали только две функции: capify и processWords. Каждая из этих функций чистая, это значит, что они , это означает, что они не зависят от своего положения в коде, из которого они вызываются. . Функции не создают побочные эффекты, которые изменяют переменные. Существует один и только один результат, возврата функции для любого набора аргументов. Из-за проведенных улучшений, новые функции очень легко протестировать, они также могут быть легко вырезаны из кода, и использованы в любом другом месте без каких-либо дополнительных модификаций.

Возможно здесь есть только одно ключевое слово, которое вы могли не знать, если до этого не сталкивались с функциональным кодом. Мы воспользовались новым методом map в Array, для того чтобы применить функцию к каждому элементу временного массива, который мы создали, когда разделили нашу строку. Map является лишь одним из немногих удобных методов, появившихся в следствие того, что современные браузеры и серверный интерпретатор JavaScript внедрили стандарты ECMAscript 5. Примените map, на месте цикла for, убрав переменную counter, и ваш код станет намного чище и читабельнее.

Начните мыслить функционально

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

  • Зависят ли мои функции от контекста, в котором они вызываются, или они чистые и независимые?
  • Могу ли я написать эти функции таким образом, чтобы они всегда возвращали один и тот же результат для заданных входных данных?
  • Уверен ли я, что мои функции не изменяют ничего лишнего?
  • Если бы я хотел использовать эти функции в другой программе, мне нужно было бы внести изменения в них?

Где лучше применять функциональное программирование в JavaScript?

Я понимаю, что чисто функциональную или чисто ООП программу писать будет не правильно. Лучше использовать сильные стороны обоих парадигм, но в каких конкретно задачах лучше подходят те или иные парадигмы? Приведите пожалуйста конкретные примеры (можно без кода).

Я знаю, что ФП широко применяется React/Redux разработчиками, но можно ли применять его совместно с Angular/RxJS? Я сейчас изучаю одновременно ООП и ФП, хотелось бы понимать, где лучше применять ту или иную парадигму. ООП легко заходит, ФП даётся тяжело, но какой-то прогресс есть и всё же кажется, что оно бессмысленно/многословно во многих моментах. Сейчас тренируюсь реализовывать различные алгоритмы при помощи ФП, например сортировка пузырьком (знаю код ужасен, но всё же он рабочий):

Тот же пример на ООП:

  • Вопрос задан 08 авг.
  • 857 просмотров

Видимо, ООП тоже не очень хорошо заходит, так как приведенном вами примере его нет.

В данный момент я слегка упарываюсь по ФП. И на самом деле это и хорошо и плохо одновременно.
Хорошо потому, что заставляю мозг думать немного иначе (привет рекурсия), плохо потому, что не все задачи поддаются решению в ФП стиле. К тому же рекурсия на больших данных переполняет стек, а хвостовую рекурсию пока реализовали только в сафари (прощай рекурсия). Однако функции, которые априори будут работать с маленьким размером данных я, иногда позволяю себе написать в рекурсивном виде.

К слову про RxJs. Я «̶н̶е̶ ̶ч̶и̶т̶а̶л̶,̶ ̶н̶о̶ ̶о̶с̶у̶ж̶д̶а̶ю̶»̶ , не изучал его, но слышал, что в библиотеке всё построено на ФП, и это одна из моих целей к изучению.

Как с ФП так и с ООП можно решать определённые типы задач. И порой код выглядит очень усложнённым, если мы пытаемся применить ФП для решения ООП задачи и наоборот.
Отчасти нам повезло, что js позволяет делать и так, и эдак, и не заключает нас в рамки использования какой-то одной концепции.

Очень годный цикл из трёх докладов на тему ООП, и почему оно нам нужно. Здесь Антон рассказывает о том, что такое доменная сложность, и этот доклад очень повлиял на моё восприятие ФП и ООП подходов. Теперь, если нужно принять решение, какой подход использовать, я спрашиваю себя, с какой доменной сложностью я столкнулся.

Согласитесь, что до возникновения определённых требований(пока не представляю каких), нет смысла реализовывать функциональность сложения чисел с использованием ООП.

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

  • решал поставленную задачу
  • был легко поддерживаемым

Функциональное программирование: структура и интерпретация. Часть I

Мне всегда хотелось написать серию статей по функциональному программированию для этого журнала, и я очень рад, что у меня наконец-то появилась такая возможность. Даже несмотря на то, что моя серия про анализ данных еще далека от завершения :). Не буду анонсировать содержание всей серии, скажу лишь, что сегодня мы поговорим о разных языках программирования, поддерживающих функциональный стиль и соответствующих приемах программирования.

Языки программирования, о которых не каждый знает

Я начал программировать еще в детстве, и годам к двадцати пяти мне казалось, что я все знаю и понимаю. Объектно ориентированное программирование стало частью моего мозга, все мыслимые книги о промышленном программировании были прочитаны. Но у меня оставалось такое ощущение, будто я что-то упустил, что-то очень тонкое и необыкновенно важное. Дело в том, что, как и многих в девяностые годы, в школе меня учили программировать на Pascal (о да, слава Turbo Pascal 5.5! — Прим. ред.), потом был C и C++. В университете Fortran и потом Java, как основной инструмент на работе. Я знал Python и еще несколько языков, но все это было не то. А серьезного образования в области Computer Science у меня не было. Однажды во время перелета через Атлантику я не мог заснуть, и мне захотелось что-то почитать. Каким-то волшебным образом у меня под рукой оказалась книга про язык программирования Haskell. Мне кажется, именно тогда я понял истинный смысл выражения «красота требует жертв».

Теперь, когда меня спрашивают, как я выучил Haskell, я так и говорю: в самолете. Этот эпизод изменил мое отношение к программированию вообще. Конечно, после первого знакомства многие вещи казались мне не вполне понятными. Пришлось напрячься и изучить вопрос более тщательно. И знаешь, прошло десять лет, многие функциональные элементы стали частью промышленных языков, лямбда-функции уже есть даже в Java, вывод типов — в С++, сопоставление с образцом — в Scala. Многие думают, что это какой-то прорыв. И в этой серии статей я расскажу тебе про приемы функционального программирования, используя разные языки и их особенности.

Интернетчики часто на потеху публике составляют всякие списки и топы. Например, «список книг, которые ты должен прочесть до тех пор, пока тебе не исполнилось тридцать». Если бы передо мной стояла задача сделать список книг по программированию, которые ты должен прочесть до тех пор, пока тебе сколько-то там не исполнилось, то первое место, безусловно, досталось бы книге Абельсона и Сассмана «Структура и интерпретация компьютерных программ». Мне даже иногда кажется, что компилятор или интерпретатор любого языка должен останавливать каждого, кто не читал эту книгу.

Мастер Йода рекомендует:  Работа с MS Access в PHP

Поэтому если и есть язык, с которого нужно начинать изучение функционального программирования, так это Lisp. Вообще, это целое семейство языков, куда входит довольно популярный сейчас язык для JVM под названием Clojure. Но в качестве первого функционального языка он не особо подходит. Для этого лучше использовать язык Scheme, который был разработан в MIT и до середины двухтысячных годов служил основным языком для обучения программированию. Хотя сейчас вводный курс с тем же названием, что упомянутая книга, был заменен на курс по Python, она все еще не потеряла своей актуальности.

Обложка знаменитой книги «Структура и интерпретация компьютерных программ»

Хакер #196. Все о Docker

Постараюсь кратко рассказать о языке Scheme и вообще об идее, стоящей за языками данной группы. Несмотря на то что Lisp очень старый (из всех языков высокого уровня старше только Fortran), именно в нем впервые стали доступны многие методы программирования, применяемые сейчас. Далее я буду использовать название Lisp, имея в виду конкретную реализацию — Scheme.

Синтаксис за две минуты

Синтаксис в языке Lisp, хм, слегка спорный. Дело в том, что идея, лежащая в основе синтаксиса, крайне проста и построена на основе так называемых S-выражений. Это префиксная запись, в которой привычное тебе выражение 2 + 3 записывается как (+ 2 3) . Это может показаться странным, но на практике дает некоторые дополнительные возможности. Кстати, (+ 2 10 (* 3.14 2)) тоже работает :). Таким образом, вся программа — это набор списков, в которых используется префиксная нотация. В случае языка Lisp сама программа и абстрактное синтаксическое дерево — «если вы понимаете, о чем я» �� — по сути, ничем не отличаются. Такая запись делает синтаксический анализ программ на Lisp очень простым.
Раз уж мы говорим о языке программирования, то следует сказать о том, как определять функции в этом языке.

Тут нужно сделать небольшое отступление. Существует одна тонкость, значимость которой в современной литературе недооценена. Нужно все-таки разделять функцию в математическом смысле и функцию, как мы ее понимаем в функциональном программировании. Дело в том, что в математике функции являются декларативными объектами, а в программировании они используются для организации процесса вычислений, то есть в каком-то смысле, скорее, представляют собой императивное знание, знание, отвечающее на вопрос «как?». Именно поэтому Абельсон и Сассман в своей книге это очень тщательно разделяют и называют функции в программировании процедурами. В современной литературе по функциональному программированию это не принято. Но я все же настоятельно рекомендую разделять эти два смысла слова «функция» хотя бы у себя в голове.

Самый простой способ определить функцию — это написать следующий код. Начнем с неприлично простого:

Да, это именно то, что ты подумал, — решение квадратного уравнения на Scheme. Но этого более чем достаточно, чтобы разглядеть все особенности синтаксиса. Здесь sq-roots — это название функции от трех формальных параметров.

На первый взгляд в конструкции let , которая используется для определения локальных переменных, слишком много скобок. Но это не так, просто сначала мы определяем список переменных, а затем выражение, в котором эти переменные используются. Здесь (list) — это пустой список, который мы возвращаем, когда корней нет, а (list x1 x2) — это список из двух значений.

Теперь о выражениях. В нашей функции sq-roots мы использовали конструкцию if . Вот здесь-то и начинается функциональное программирование.

Дело в том, что в отличие от императивных языков, таких как C, в функциональных языках if — это выражение, а не оператор. На практике это означает, что у него не может отсутствовать ветка else. Потому что выражение всегда должно иметь значение.

Нельзя рассказать про синтаксис, не поговорив о синтаксическом сахаре. В языках программирования синтаксическим сахаром называют конструкции, которые не являются необходимыми, а лишь облегчают чтение и переиспользование кода. Для начала приведем классический пример из языка C. Многие знают, что массивы не обязательное средство выражения, так как есть указатели. Да, действительно, массивы реализованы через указатели, и a[i] для языка C — это то же самое, что и *(a + i) . Данный пример вообще довольно необычный, с ним связан забавный эффект: так как операция сложения остается коммутативной в случае указателей, то последнее выражение — это то же самое, что и *(i + a) , а это может быть получено при удалении синтаксического сахара из выражения i[a] ! Операция удаления синтаксического сахара в английском языке называется специальным словом desugaring.

Возвращаясь к языку Scheme, следует привести важный пример синтаксического сахара. Для определения переменных, как и в случае функций, используется ключевое слово (в Lisp и Scheme это называется специальной формой) define . К примеру, (define pi 3.14159) определяет переменную pi . Вообще говоря, точно так же можно и определять функции:

это то же самое, что и

Последняя строчка выглядит чуть более легко читаемой по сравнению с вариантом, в котором используется лямбда-выражение. Однако понятно, что достаточно иметь первый вариант, а второй необязателен. Почему именно первый важнее? Потому что одно из самых базовых свойств функциональных языков — что функции в них являются объектами первого класса. Последнее означает, что функции можно передавать в качестве аргумента и возвращать в качестве значения.

Если посмотреть на let с точки зрения лямбда-выражения, то легко заметить следующее соответствие:

Здесь оба выражения можно считать эквивалентными, а apply просто применяет функцию к списку аргументов.

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

Функциональные языки бывают чистыми и нечистыми. Чистые функциональные языки сравнительно редки, к ним относятся в первую очередь Haskell и Clean. В чистых языках нет побочных эффектов. На практике это означает отсутствие присваивания и ввода-вывода в том виде, к которому мы привыкли. Это создает ряд трудностей, хотя в уже упомянутых языках это решено довольно хитроумно, и на этих языках пишут код с большим количеством ввода-вывода. Языки типа Lisp, OCaml или Scala допускают функции с побочными эффектами, и в этом смысле данные языки зачастую более практичны.

Наша задача — изучить основные приемы функционального программирования на Scheme. Поэтому мы будем писать чисто функциональный код, без использования генератора случайных чисел, ввода-вывода и функции set! , которая позволят менять значения переменных. Обо всем этом можно прочитать в книге SICP. Сейчас остановимся на самом существенном для нас.

Первое, что смущает начинающего в функциональном программировании, — это отсутствие циклов. А как же быть? Многих из нас учат, что рекурсия — это плохо. Аргументируется это тем, что рекурсия в обычных языках программирования обычно реализована неэффективно. Дело в том, что в общем случае следует различать рекурсию как технический прием, то есть вызов функции из самой себя, и рекурсию как процесс. В функциональных языках поддерживается оптимизация хвостовой рекурсии или, как иногда говорят, рекурсии с аккумулятором. Это можно проиллюстрировать на простом примере.

Пускай у нас есть две функции — succ и prev . Первая возвращает число, на 1 большее, чем аргумент, а вторая — на 1 меньшее. Теперь попробуем определить операцию сложения, причем двумя способами:

В чем разница между первым и вторым случаем? Дело в том, что если рассмотреть способ вычисления для первого случая по шагам, то можно увидеть следующее:

Во втором случае мы будем иметь примерно следующее:

Несмотря на то что и в том и другом случае результат одинаков, процесс вычисления кардинально отличается. В первом случае количество используемой памяти не меняется, а во втором растет линейным образом. Первый процесс является итеративным, а второй — рекурсивым. Так, для написания эффективных программ на функциональных языках нужно использовать хвостовую рекурсию для того, чтобы избежать переполнения стека.

Списки

Один из важнейших элементов функционального программирования, наряду с рекурсией, — списки. Они обеспечивают основу для сложных структур данных. Как и в других функциональных языках, списки являются односвязными по принципу голова — хвост. Для создания списка используется функция cons , а для доступа к голове и хвосту списка — функции car и cdr соответственно. Так, список (list 1 2 3) — это не что иное, как (cons 1 (cons 2 (cons 3 ‘()))) . Здесь ‘() — пустой список. Таким образом, типичная функция обработки списка выглядит так:

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

Функции высших порядков

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

Функция map применяет функцию f к каждому элементу списка. Как бы это странно ни выглядело, но теперь мы можем выразить функцию вычисления длины списка length через sum и map :

Если ты вдруг сейчас решил, что все это как-то слишком просто, то давай подумаем вот над чем: как сделать реализацию списков, используя функции высших порядков?

То есть нужно реализовать функции cons , car и cdr так, чтобы они удовлетворяли следующему соотношению: для любого списка lst верно, что значение (cons (car lst) (cdr lst)) совпадает с lst . Это можно сделать следующим образом:

Как это работает? Здесь функция cons возвращает другую функцию, которая имеет один параметр и в зависимости от этого возвращает либо первый, либо второй аргументы. Легко проверить, что необходимое соотношение выполняется для этих функций.

Использование quote и метапрограммирование

Одна приятная особенность языка Lisp делает его необыкновенно удобным для написания программ, которые занимаются преобразованием других программ. Дело в том, что программа состоит из списков, а список — это основная структура данных в языке. Существует способ просто «закавычить» текст программы, чтобы она воспринималась как список атомов.

Атомы — это просто символьные выражения, к примеру (‘hello ‘world) , что то же самое, что и ‘(hello world) , или в полной форме (quote (hello world)) . Несмотря на то что в большинстве диалектов Lisp есть строки, иногда можно обходиться quote . Что более важно, с помощью такого подхода можно упростить кодогенерацию и обработку программ.

Для начала попробуем разобраться с символьными вычислениями. Обычно под этим понимают системы компьютерной алгебры, которые способны обращаться с символьными объектами, с формулами, уравнениями и прочими сложными математическими объектами (таких систем много, основными примерами служат системы Maple и Mathematica).

Можно попробовать реализовать символьное дифференцирование. Я думаю, правила дифференцирования представляет себе каждый, кто близок к окончанию школы (хотя на самом деле все чуть сложнее — здесь мы будем вычислять частную производную, просто считая другие переменные константами, но это нисколько не усложняет суть дела).

Так что я лишь приведу пример кода, который бы показывал суть дела, детали оставлю читателю (который, как я надеюсь, тщательно изучит книгу «Структура и интерпретация компьютерных программ»).

Здесь функция deriv представляет собой реализацию алгоритма дифференцирования так, как его проходят в школе. Данная функция требует реализации функций number? , variable? и так далее, которые позволяют понять, какую природу имеет тот или иной элемент выражения. Также нужно реализовать дополнительные функции make-product и make-sum . Здесь используется пока неизвестная нам конструкция cond — это аналог оператора switch в таких языках программирования, как C и Java.

Перед тем как мы перейдем к реализации недостающих функций, стоит отметить, что в функциональном программировании довольно часто используется top-down подход к разработке. Это когда сначала пишутся самые общие функции, а затем небольшие функции, отвечающие за детали реализации.

Реализация данных функций не требует специальных комментариев, за исключением, может быть, функций cadr и caddr . Это не что иное, как функции, которые возвращают второй и третий элементы списка соответственно.

Если воспользоваться интерактивным интерпретатором Scheme, то легко убедиться, что полученный код работает правильно, но без упрощения выражений:

Для тривиальных случаев (например, умножение на 0) задача упрощения решается довольно легко. Этот вопрос остается читателю. Большинство примеров в этой статье взяты из книги SICP, поэтому в случае возникновения трудностей можно просто обратиться к источнику (книга находится в открытом доступе).

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

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

Эта функция прекрасно работает с правильно сформированными определениями функций:

Однако это не работает для обычных определений, таких как (define x 5) .
Если мы хотим удалить синтаксический сахар в большой программе, содержащей множество различных определений, то мы должны реализовать дополнительную проверку:

Такую проверку можно встроить прямо в функцию desugar-define , сделав так, чтобы в случае, если определение не нуждается в удалении синтаксического сахара, оно просто бы не менялось (данное тривиальное упражнение остается читателю). После чего можно обернуть всю программу в список и использовать map :

Заключение

В данной статье я не ставил себе задачу рассказать про Scheme сколь-нибудь подробно. Мне прежде всего хотелось показать несколько интересных особенностей языка и привлечь читателя к изучению функционального программирования. Этот чудесный язык при всей его простоте имеет свое очарование и особенности, которые делают программирование на нем очень увлекательным. Что касается инструмента для работы со Scheme, то сильные духом могут замахнуться на MIT-Scheme, а остальные — пользуйтесь прекрасной учебной средой Dr. Racket. В одной из следующих статей я обязательно расскажу, как написать собственный интерпретатор Scheme.

Функциональное программирование с примерами на JavaScript. Часть первая. Основные техники функционального программирования

Группа: Главные администраторы
Сообщений: 14349
Регистрация: 12.10.2007
Из: Twilight Zone
Пользователь №: 1

JavaScript мощный, но недопонятый язык программирования. Людям нравится говорить, что он является объектно-ориентированным языком программирования или является функциональным языком. Другим нравится говорить, что он не является объектно ориентированным или не является функциональным языком программирования. Некоторые скажут что он относится к обоим парадигмам или ни к одной из них — но, давайте отложим на пока этот спор в сторонку.

Давайте зададимся миссией: писать на JavaScript, используя принципы функционального программирования настолько, насколько это позволяется язык.

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

Функциональное программирование (сильно) недопонято в JS-мире

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

Я думаю, что это результат того, что большинство языков программирования используемых для веб-разработки на стороне сервера, уходят корнями в Си, и большинство согласится, что он не функциональный язык программирования.

Тут существует два момента. Первый момент может быть продемонстрирован следующим примером на jQuery, который является широко используемым:

$(«.signup»).click(function(event) <
$(«#signupModal»).show();
event.preventDefault();
>);

Эй, посмотрите-ка на это. Мы только что выполнили передачу анонимной функции как аргумента, также известной в JavaScript под гадким названием callback-функция.

Некоторые называют это функциональным программированием. Так ли это? Не совсем!

Этот пример является демонстрацией ключевой фишки функционального программирования: функция как параметр. С другой стороны этот пример также идёт вразрез почти с любой другой из парадигм функционального программирования с какими вообще можно пойти вразрез в трёх строчках примера.

Второй момент чуточку более коварный. Читая это, некоторые модные JS разработчики думают про себя:

Ну! Я уже все знаю про функциональное программирование. Я использую Underscore.js во всех моих проектах

Underscore.js — довольно популярная JavaScript библиотека, используемая повсеместно. Для примера, скажем, что у меня имеется набор слов и нам необходим соответствующий набор из первых двух букв каждого слова. Вместе с Underscore.js это довольно легкая задача:

var firstTwoLetters = function(words) <
return _.map(words, function(word) <
return _.first(word, 2);
>);
>;

Видите! Посмотите на это JavaScript-вуду. Я использую эти чудные функциональные утилиты как _.map и _.first. Что ты теперь ответишь на ЭТО, Леланд? 1

Хотя подчеркивание и функции вроде _.map — полезные функциональные парадигмы, однако способ их совместного использования в этом примере выглядит… многословным и тяжело воспринимается мной. Неужели нам все это нужно?

Если мы начнем думать о вещах чуточку более «функционально», то, наверное, взяв пример выше, мы получим это:

// . капелька магии
var firstTwoLetters = map(first(2));

Если вы подумаете об этом, то увидите всю ту же информацию в одной строчке, что и в пяти строках выше. words и word просто параметры/заполнители. Реальное мясо логики заключается в комбинировании функции map, функции first и константы 2 в осмысленном виде.

Некоторые, глядя на этот пример, могут задуматься: что ещё за «капелька магии». В конце концов, вставлять любой пример с «капелькой магии» как в примере это как… своего рода обман, а?

Что ж, я собираюсь посвятить парочку следующих постов, объясняя эту «капельку магии», так что если заинтригованы — будьте добры продолжать.

Эта серия постов призвана помочь другим научиться заимствовать красоту из языков функционального программирования в контексте JavaScript.

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

Читать далее -> Часть 2: что делает язык «функциональным»?

Об ошибках и неточностях просьба писать сюда

Функциональное программирование в javascript [закрыт]

Изучаю Erlang, попробовал написать что-то вроде аналога встроенной функции «свертки» lists:foldl , на javascript:

В этом примере «повышается уровень абстракции», соблюдена чистота функции. Приемлимо ли писать (или пытаться писать) код полностью в функциональном стиле, как в Erlang, на javascript? К чему это может привести в конечном счете?

Закрыт по причине того, что необходимо переформулировать вопрос так, чтобы можно было дать объективно верный ответ участниками user181100, Grundy, aleksandr barakin, user194374, Dmitriy Simushev 28 июл ’16 в 10:42 .

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

Добавить комментарий