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

Возможно вам приходилось слышать такие слова как «Clojure», «Scala» или «Erlang», а может даже фразы вроде «В Java теперь появились лямбда-функции». И возможно вы даже знаете, что все это связано с каким-то функциональным программированием. Если вы частым посетителем любых программистских сообществ, то эти темы должны были подниматься в последнее время все чаще. Однако, если вы введете в гугле «функциональное программирование», то не увидите

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

Давным-давно, когда компьютеры были очень медленными

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

  1. Взять архитектуру Фон Неймана и добавить абстрактность;
  2. Взять математику и убрать абстрактность

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

Но времена изменились

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

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

В этом разделе я не буду знакомить вас с функциональным программированием или что-то вроде того. Прочитав до конца вы сами должны будете решить, что это такое, хорошо это или плохо и как начать программировать функционально. Допустим вы впервые слышите словосочетание «функциональное программирование». Вдумайтесь в название, может показаться, что это какое-программирования, которое как-то связано с функциями, и вы не ошибетесь, вы можете только недооценить то, насколько сильно это программирование связано с функциями. Здесь функция на функции и функцией погоняют. Помните конструкции типа «f ∘ g» по высшей математике? Вот как-то так тут все и делается. Далеко неполный список ключевых понятий ФП:

  • функции первого класса;
  • функции более высокого порядка;
  • чистые функции;
  • замыкания;
  • неизменное состояние.

Но вам сейчас важнее не запомнить красивые названия, а понять, что означает то или иное понятие.

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

Вы сохраняете функцию, которая получает a и b, а возвращает a + b, в переменную add.

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

или:

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

Чистые функции — функция, которая не изменяет никакие данные, а просто берет и возвращает какие-то значения, как наши любимые функции в математике. То есть если вы передаете функции f число 2, а она возвращает вам 10, это означает, что она возвращает 10 всегда в подобной ситуации. Результаты, выдаваемые такой функцией, никак не зависят от окружения или порядка вычислений. Также использование чистых функций не влечет за собой побочных эффектов в различных частях программы, поэтому это очень мощный инструмент.

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

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

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

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

ООП больше нас не спасет

Долгожданное время, когда программы выполняются одновременно на множестве машин, наступило. Но мы отстаем. То, как мы решаем вопрос параллелизма и распределенных вычислений, только добавляет сложности программированию. Чтобы исправить ситуацию нужно более простой и надежный способ, чем парадигма ООП. Еще не забыли ключевые понятия ФП, которые мы рассмотрели чуть раньше? Чистые функции, неизменное состояние, вот это все? Прекрасно. А что если я скажу вам, что не обязательно обрабатывать тысячи результатов, запуская одну и ту же функцию на тысячи ядер? Чтобы сделать то же самое с помощью функционального программирования достаточно одного. Жизнь уже не будет такой, как раньше.

Почему ООП?

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

Я не работаю с облаками, нужен мне ваш ФП?

Да. Вы же хотите писать лучше, а проблемы решать проще?

Я пытался, но получилось как-то слишком сложно и нечитаемым.

Сначала все трудно. Думаю, вам приходилось овладев одным языком, браться за другой и вы изо всех сил пытались в нем разобраться. Скорее всего это были объектно-ориентированные языки, так что перед началом изучения нового вы уже знали основные идиомы, приемы и владели пониманием того, что такое условие, а что такое цикл. Изучение функционального программирования — это изучение программирования с нуля, с азов. Часто приходится слышать, что код, который написан на языке функционального программирования, трудно читать. Если вы только и делали, что программировали на императивных языках, то код на ФП вам покажется шифрограммой. Но дело не в том, что там все зашифровано от греха подальше, а в том, что вы просто не знаете основных идиом. Что же попробуйте узнать, а затем посмотреть на тот же код снова. Посмотрите на код одной и той же программы, написанные на Haskell и JS в стиле императивного программирования:

Это простая программа, которая выдает приветственное сообщение, если пользователь вводит цифру 7 или выдает ошибку в противном случае. Удивительно, но для Haskell достаточно 2 строки (не считая первого — это аннотация к типам). Вы сможете так же, если разберетесь с таким понятием как «сопоставление с образцом», которое, кстати, не является особенностью ФП, но именно в нем используется повсеместно. Что делает Haskell в коде выше: О, похоже у нас тут семерка, что же поздравлю пользователя с его маленькой победой, чего бы я не сделал, если бы это была не семерка. Вообще-то также думает и JS, но Haskell работает методом сравнения с паттернами , которые предоставил программист. Преимущества такого метода перед традиционными if-else решениями раскрываются во всей красе, когда возникает необходимость работать с большими объемами структурированных данных.

В этом примере функция plus1 принимает список int-значений и добавляет к каждому единицу. Сопоставление происходит с пустым списком [] и непустое согласно паттерна: первому элементу дается имя x, а остальному списку — xs, затем производится добавление и объединение с рекурсивным вызовом. Думаю, что было бы сложно написать функцию plus1, используя ООП, в две строки и сохранить тот же уровень читабельности.

Вот такая получилась большая статья о функциональном программировании. Следите за обновлениями Prologistic.com.ua

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

  1. Сейчас изучаю clojure и эта статья многое поставила на свои места. Спасибо!
    (def add
    (fn [a]
    (fn [b] (+ a b))))
    (def add2 (add 2))
    (add2 5) => 5 😉

    (def func
    (fn [vector] (map inc vector)))
    (func [0 1 2 3]) => (1 2 3 4)

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *