Tw-city.info

IT Новости
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

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

Программирование на C++ в функциональном стиле

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

Под программированием в функциональном стиле я не имею в виду, что оно является строго функциональным, а лишь то, что в C++ легко использовать многие из функциональных строительных блоков. В данной статье основное внимание уделяется одной из важнейших конструкций функционального программирования: работе со значениями (values) в противоположность идентификациям (identities). Я рассмотрю мощную поддержку, которая всегда была в C++ для работы со значениями, потом покажу, как в новом стандарте C++ 11 эта поддержка расширена с помощью лямбд. Наконец, я дам введение по методу работы с неизменяемыми (immutable) структурами данных, который обеспечивает не только свойственное C++ быстродействие, но и защиту, уже достаточно давно реализованную в функциональных языках.

Значения в сравнении с идентификациями

Сначала позвольте мне пояснить, что я имею в виду под работой со значениями по сравнению с идентификациями. Простые значения вроде 1, 2 и 3 легко идентифицировать. Я мог бы также сказать, что 1, 2 и 3 являются константными целочисленными значениями. Однако это было бы избыточно, так как все значения — на самом деле константы, и сами значения никогда не изменяются (т. е. 1 — всегда 1, и 1 никогда не превратится в 2). С другой стороны, значение, сопоставленное с идентификацией, измениться может (x может быть сейчас равно 1, но позднее стать равным 2).

К сожалению, перепутать значения и значимые типы (value types) очень легко. Значимые типы передаются по значению, а не по ссылке. Хотя здесь я намерен сосредоточиться на значениях, а не на механизме, задействованном в их использовании или копировании, будет полезно посмотреть, как значимые типы участвуют в сохранении концепции значений и идентификаций.

Код на рис. 1 демонстрирует простое использование значимого типа.

Рис. 1. Использование значимого типа

Небольшое изменение, и переменная y может стать ссылочным типом (reference type), что кардинально меняет взаимосвязь между x и y, как показано на рис. 2.

Рис. 2. Использование ссылочного типа

Как видно на рис. 3, C++ также поддерживает модификатор const, который не дает программисту вносить изменения в переменную и тем самым еще больше защищает концепцию значения. (Однако, как и в большинстве других случаев, в C++ есть минимум один способ разрушить эту защиту. Подробнее на эту тему ищите описание const_cast, предназначенной для работы с более старым кодом, который не является «const-корректным».)

Рис. 3. Модификатор const

Заметьте, хотя на рис. 3 переменная y передается по ссылке, значение y защищено на этапе компиляции модификатором const. Это дает программистам на C++ эффективный способ передачи больших объектов и в то же время позволяет работать с их значениями, а не идентификациями.

Благодаря модификатору const в C++ есть неизменяемые типы данных, напоминающие таковые в большинстве языков функциональность программирования. Однако иметь дело с этими типами данных трудно. Более того, создавать детальные (deep) (полные) копии больших объектов при каждом незначительном изменении неэффективно. Тем не менее, должно быть ясно, что в стандартном C++ всегда была концепция работы со значениями (даже если ее чистота выдерживалась не полностью).

Заметьте, что поддержка значимых типов расширяется до охвата пользовательских типов через конструкторы копий и операторы присваивания. Конструкторы копий и операторы присваивания в C++ позволяют создавать детальную копию экземпляра пользовательского типа. Учитывайте, что хотя в C++ можно реализовать конструкторы копий для создания поверхностной копии (shallow copy), вы должны заботиться о сохранении семантики значений.

Поддержка программирования в функциональном стиле в C++ 11

В C++ 11 появилось целый ряд новых средств для программирования в функциональном стиле. Возможно, самое важное, что C++ теперь поддерживает лямбды (также называемые замыканиями [closures] и анонимными функциями). Лямбды позволяют структурировать код такими способами, которые ранее были бы непрактичными. Эта функциональность и раньше была доступна через функторы — мощные, но не столь удобные в использовании средства. (На самом деле «за кулисами» лямбды C++ записывают анонимные функторы.) На рис. 4 показано, как лямбды позволили улучшить наш код на простом примере, использующем STL-библиотеку (C++ standard library).

Рис. 4. Использование лямбд

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

Хотя C++ — лишь один из многих императивных языков, в которых теперь появились лямбды, изюминка лямбд в C++ в том, что (по аналогии с лямбдами в языках функционального программирования) они позволяют защищать концепцию работы со значениями в противоположность идентификациям. Тогда как языки функционального программирования достигают этой цели за счет неизменяемости переменных, C++ делает это, предоставляя контроль над захватом (capture). Рассмотрим код на рис. 5.

Рис. 5. Захват по ссылке

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

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

Также заметьте: чтобы сохранить переменную ctr «живой» для использования лямбдой вне цикла for, объявление переменной ctr следует вынести из цикла for и поместить перед ним. Хотя ненкоторые языки исключают необходимость в выносе переменных значимого типа в соответствующую область видимости, это на самом деле не решает проблему, которая заключается в том, что лямбде нужно использовать значение ctr в противоположность идентификации этой переменной. (В других языках есть обходные пути, в том числе создание явной копии и ее запись во временную переменную. Однако это немного затуманивает то, что происходит в коде, и повышает риск ошибок, так как исходная переменная тоже захватывается т из-за этого по-прежнему доступна для использования.)

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

Табл. 1. Синтаксис C++ для управления захватом лямбды

Ничего не захватывается

(как раз то, что мне было нужно в первом примере с лямбдой)

Захват всего по ссылке

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

Захват всего по значению

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

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

Неизменяемые типы данных

Чего не хватает, так это эффективных неизменяемых структур данных, имеющихся в некоторых языках функционального программирования. В этих языках неизменяемые структуры данных эффективны, даже когда они очень большие, потому что используют общие данные. Создание в C++ структур данных, совместно использующих данных, — дело тривиальное: вы просто динамически выделяете память под данные, и у каждой структуры есть указатели на эти данные. Увы, управлять жизненным циклом общих переменных труднее (именно поэтому, кстати, сборщики мусора стали такими популярными). К счастью, C++ 11 предоставляет элегантное решение для работы с общими переменными через класс шаблона std::shared_ptr, как показано на рис. 6.

Читать еще:  Язык декларативного программирования xaml скачать бесплатно

Рис. 6. Общие переменные

Код на рис. 6 иллюстрирует простое использование std::shared_ptr и его вспомогательной функции std::make_shared. С помощью std::shared_ptr можно легко разделять данные между структурами, не боясь утечки памяти (при условии, что вы избегаете круговых ссылок). Заметьте, что std::shared_ptr предоставляет базовые гарантии безопасности в многопоточной среде (thread-safety) и выполняется быстро, так как использует архитектуру без блокировок. Однако учитывайте, что эти базовые гарантии не распространяются автоматически на объект, на который указывает std::shared_ptr. Тем не менее, std::shared_ptr гарантирует, что он не уменьшит степень безопасности этого объекта в многопоточной среде. Неизменяемые объекты по своей сути обеспечивают серьезные гарантии безопасности в многопоточной среде, поскольку они никогда не изменяются после создания. Так что, когда вы используете std::shared_ptr с неизменяемым объектом, эта комбинация сохраняет все гарантии безопасности неизменяемого объекта в многопоточной среде.

Теперь я могу легко создать неизменяемый класс, потенциально способный разделять данные (рис. 7).

Рис. 7. Неизменяемый класс для разделения данных

Код на рис. 7 довольно длинный, но большая его часть для конструкторов и операторов присваивания стереотипна. Последние две функции являются ключевыми для того, чтобы сделать объект неизменяемым. Заметьте, что методы SetS и SetD возвращают новый объект, что оставляет исходный объект неизменным. (Хотя включение методов SetS и SetD в виде членов класса удобно, это немного вводит в заблуждение, поскольку на самом деле они не изменяют исходный объект. Более четкое решение см. в ImmutableVector рис. 8 и 9.) Класс Immutable в действии показан на рис. 10.

Рис. 8. Использование «интеллектуального» класса шаблона ImmutableVector

Рис. 9. Методы для операций с ImmutableVector

Рис. 10. Класс Immutable в действии

Заметьте, что объект b использует ту же строку, что и объект a (обе строки находятся по одинаковому адресу). Включение дополнительных полей с сопоставленными get- и set-аксессорами осуществляется тривиальным образом. Хотя этот код вполне хорош, его масштабирование до контейнеров немного затруднительно. Например, «наивная» реализация ImmutableVector могла бы поддерживать список общих указателей, представляющих каждый элемент массива. Если «наивный» ImmutableVector изменяется, вам придется продублировать весь массив общих указателей, что повлечет за собой дополнительные издержки, так как каждый элемент shared_ptr массива потребует увеличения своего счетчика ссылок.

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

Рис. 11. Сравнение «наивной» и «интеллектуальной» реализаций ImmutableVector

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

    Переводы, 23 января 2017 в 13:43

Если вы такой же разработчик, как и я, то наверняка сперва изучали парадигму ООП. Первым вашим яыком были Java или C++ — или, если вам повезло, Ruby, Python или C# — поэтому вы наверняка знаете, что такое классы, объекты, экземпляры и т.д. В чём вы точно не особо разбираетесь, так это в основах той странной парадигмы, называющейся функциональным программированием, которая существенно отличается не только от ООП, но и от процедурного, прототипно-ориентированного и других видов программирования.

Функциональное программирование становится популярным — и на то есть причины. Сама парадигма не нова: Haskell, пожалуй, является самым функциональным языком, а возник он в 90-ых. Такие языки, как Erlang, Scala, Clojure также попадают под определение функциональных. Одним из основных преимуществ функционального программирования является возможность написания программ, работающих конкурентно (если вы уже забыли, что это — освежите память прочтением статьи о конкурентности), причём без ошибок — то есть взаимные блокировки и потокобезопасность вас не побеспокоят.

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

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

1. Все функции — чистые

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

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

Первое правило понятно — если я вызываю функцию sum(2, 3) , то ожидаю, что результат всегда будет равен 5. Как только вы вызываете функцию rand() , или обращаетесь к переменной, не определённой в функции, чистота функции нарушается, а это в функциональном программировании недопустимо.

Второе правило — никаких побочных эффектов — является более широким по своей природе. Побочный эффект — это изменение чего-то отличного от функции, которая исполняется в текущий момент. Изменение переменной вне функции, вывод в консоль, вызов исключения, чтение данных из файла — всё это примеры побочных эффектов, которые лишают функцию чистоты. Может показаться, что это серьёзное ограничение, но подумайте ещё раз. Если вы уверены, что вызов функции не изменит ничего “снаружи”, то вы можете использовать эту функцию в любом сценарии. Это открывает дорогу конкурентному программированию и многопоточным приложениям.

2. Все функции — первого класса и высшего порядка

Эта концепция — не особенность ФП (она используется в Javascript, PHP и других языках) — но его обязательное требование. На самом деле, на Википедии есть целая статья, посвящённая функциям первого класса. Для того, чтобы функция была первоклассной, у неё должна быть возможность быть объявленной в виде переменной. Это позволяет управлять функцией как обычным типом данных и в то же время исполнять её.

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

3. Переменные неизменяемы

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

4. Относительная прозрачность функций

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

Пусть у нас есть Java-функция, которая складывает 3 и 5:

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

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

5. Функциональное программирование основано на лямбда-исчислении

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

  1. В лямбда-исчислении все функции могут быть анонимными, поскольку единственная значимая часть заголовка функции — это список аргументов.
  2. При вызове все функции проходят процесс каррирования. Он заключается в следующем: если вызывается функция с несколькими аргументами, то сперва она будет выполнена лишь с первым аргументом и вернёт новую функцию, содержащую на 1 аргумент меньше, которая будет немедленно вызвана. Этот процесс рекурсивен и продолжается до тех пор, пока не будут применены все аргументы, возвращая финальный результат. Поскольку функции являются чистыми, это работает.
Читать еще:  Программирование для windows 10

Как я уже говорил, лямбда-исчисление на этом не заканчивается — но мы рассмотрели лишь ключевые аспекты, связанные с ФП. Теперь, в разговоре о функциональном программировании вы сможете блеснуть словечком “лямбда-исчисление”, и все подумают, что вы шарите 🙂

Заключение

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

Если вы хотите узнать о функциональном программировании побольше, то советуем вам ознакомиться с примерами использования принципов ФП в JavaScript (часть 1, часть 2), а также с циклом статей, посвящённым функциональному C#.

Обязательно изучите функциональное программирование в 2017 году

Функциональное программирование существует уже очень давно, начиная с появления языка программирования Lisp в 50-х годах прошлого века. И, если вы заметили, на протяжении последних двух лет такие языки, как Clojure, Scala, Erlang, Haskell и Elixir, создают много шума и привлекают к себе внимание.

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

Краткая история функционального программирования

Как мы уже говорили, функциональное программирование берет свое начало еще в 50-х годах с момента создания Lisp для работы в серии научных компьютеров IBM700/7000. Lisp представил множество парадигм и особенностей, которые теперь мы связываем с функциональным программированием, и хотя мы можем назвать Lisp дедушкой функционального программирования мы можем копнуть глубже и взглянуть на еще большую общность между всеми функциональными языками программирования — лямбда-исчисление.

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

Лямбда-исчисление обладает свойством полноты по Тьюрингу, то есть является универсальной моделью вычислений, которая может быть использована для моделирования любой одноленточной машины Тьюринга. Ее тезка, греческая буква лямбда (λ), используется в лямбда-выражениях и лямбда-условиях для обозначения связывания переменной с функцией. — Википедия

Лямбда-исчисление — удивительно простая, но мощная концепция. В основе лямбда-исчисления лежат два понятия:

  • Функциональная абстракция, использующаяся для обобщения выражений посредством введения имен (переменных)
  • Функциональное применение, которое используется для вычисления обобщенных выражений путем присвоения переданных имен к определенным значениям

В качестве примера давайте рассмотрим функцию f с одним аргументом, увеличивающую аргумент на единицу:

Допустим, мы хотим применить функцию к числу 5 . Тогда функцию можно читать следующим образом:

Основы функционального программирования

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

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

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

Затем мы легко можем вызвать эту функцию:

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

Функции высшего порядка — функции, принимающие одну или несколько функций в качестве аргументов и/или возвращающие новую функцию. Для демонстрации концепции давайте снова воспользуемся нашей функцией double :

В этом примере Enum.map в качестве первого аргумента принимает перечисляемый — список, а в качестве второго — функцию, которую мы только что определили. Затем Enum.map применяет функцию к каждому элементу списка. В результате мы получаем:

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

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

Как и прежде, давайте воспользуемся Elixir для иллюстрации:

В примере выше наша переменная tuple никогда не изменит своего значения. В третьей строке put_elem возвращает совершенно новый tuple без изменения значения оригинала.

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

  • Функциональное программирование существует уже давно (с начала 50-х годов)
  • Функциональное программирование основано на математических концепциях, в частности на лямбда-исчислениях
  • Функциональное программирование считалось слишком медленным по сравнению с императивными языками
  • Функциональное программирование возвращается

Применение функционального программирования

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

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

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

Но не надо верить мне на слово, вы можете найти достаточно доказательств, посмотрев на технологические новости стартапов, таких как WhatsApp и Discord:

  • 900 миллионов пользователей WhatsApp поддерживают всего лишь 50 инженеров, используя Erlang
  • Discord подобным образом обрабатывают более миллиона запросов в минуту с использованием Elixir

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

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

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

В первом случае основные принципы понятны: вы оперируете математической логикой для вывода новых фактов и состояний из уже известных. Ярким примером такого языка является 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). Так что если решили направить свою дальнейшую карьеру в это русло, то самое время обложиться описанной выше литературой и оставить свой след в чьей-то виртуальной голове.

Элементы функционального программирования

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

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

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

Что такое функциональное программирование?

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

Как отмечает Дэвид Мертц (David Mertz) в своей статье о функциональном программировании на Python , «функциональное программирование — программирование на функциональных языках ( LISP , ML, OCAML, Haskell, . )», основными атрибутами которых являются:

  • «Наличие функций первого класса» (функции наравне с другими объектами можно передавать внутрь функций).
  • Рекурсия является основной управляющей структурой в программе.
  • Обработка списков (последовательностей).
  • Запрещение побочных эффектов у функций, что в первую очередь означает отсутствие присваивания (в «чистых» функциональных языках)
  • Запрещение операторов, основной упор делается на выражения. Вместо операторов вся программа в идеале — одно выражение с сопутствующими определениями.
  • Ключевой вопрос: что нужно вычислить, а не как.
  • Использование функций более высоких порядков (функции над функциями над функциями).

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

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

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

Кстати, бинарные операции » + «, » — «, » * «, » / «, которые записываются в выражениях, являются «математическими» функциями над двумя аргументами — операндами. Их используют настолько часто, что синтаксис языка программирования имеет для них более короткую запись . Модуль operator позволяет представлять эти операции в функциональном стиле:

Ссылка на основную публикацию
Adblock
detector