В информатике функциональное программирование — это парадигма программирования , в которой программы строятся путем применения и составления функций . Это декларативная парадигма программирования, в которой определения функций представляют собой деревья выражений , которые сопоставляют значения с другими значениями, а не последовательность императивных операторов , которые обновляют текущее состояние программы.
В функциональном программировании функции рассматриваются как граждане первого класса , что означает, что они могут быть привязаны к именам (включая локальные идентификаторы ), переданы в качестве аргументов и возвращены из других функций, как и любой другой тип данных . Это позволяет писать программы в декларативном и компонуемом стиле, где небольшие функции объединяются модульным образом .
Функциональное программирование иногда рассматривается как синоним чисто функционального программирования , подмножества функционального программирования, которое рассматривает все функции как детерминированные математические функции или чистые функции . Когда чистая функция вызывается с некоторыми заданными аргументами, она всегда будет возвращать тот же результат и не может быть затронута каким-либо изменяемым состоянием или другими побочными эффектами . Это контрастирует с нечистыми процедурами , распространенными в императивном программировании , которые могут иметь побочные эффекты (такие как изменение состояния программы или получение ввода от пользователя). Сторонники чисто функционального программирования утверждают, что, ограничивая побочные эффекты, программы могут иметь меньше ошибок , их легче отлаживать и тестировать , и они больше подходят для формальной проверки . [1] [2]
Функциональное программирование имеет свои корни в академической среде , развиваясь из лямбда-исчисления , формальной системы вычислений, основанной только на функциях. Функциональное программирование исторически было менее популярно, чем императивное программирование, но многие функциональные языки сегодня находят применение в промышленности и образовании, включая Common Lisp , Scheme , [3] [4] [5] [6] Clojure , Wolfram Language , [7] [8] Racket , [9] Erlang , [10] [11] [12] Elixir , [13] OCaml , [14] [15] Haskell , [16] [17] и F# . [18] [19] Lean — это функциональный язык программирования, обычно используемый для проверки математических теорем. [20] Функциональное программирование также является ключом к некоторым языкам, которые добились успеха в определенных областях, например, JavaScript в Интернете, [21] R в статистике, [22] [23] J , K и Q в финансовом анализе и XQuery / XSLT для XML . [24] [25] Специфичные для предметной области декларативные языки, такие как SQL и Lex / Yacc, используют некоторые элементы функционального программирования, такие как запрет на изменяемые значения . [26] Кроме того, многие другие языки программирования поддерживают программирование в функциональном стиле или реализовали функции функционального программирования, например, C++11 , C# , [27] Kotlin , [28] Perl , [29] PHP , [30] Python , [31] Go , [32] Rust , [33] Raku , [34] Scala , [35] и Java (начиная с Java 8) . [36]
Благодаря своей компоновке парадигмы функционального программирования могут быть пригодны для архитектур, основанных на микросервисах . [37]
Лямбда -исчисление , разработанное в 1930-х годах Алонзо Чёрчем , представляет собой формальную систему вычислений , построенную на применении функций . В 1937 году Алан Тьюринг доказал, что лямбда-исчисление и машины Тьюринга являются эквивалентными моделями вычислений, [38] показав, что лямбда-исчисление является полным по Тьюрингу . Лямбда-исчисление составляет основу всех функциональных языков программирования. Эквивалентная теоретическая формулировка, комбинаторная логика , была разработана Моисеем Шёнфинкелем и Хаскеллом Карри в 1920-х и 1930-х годах. [39]
Позднее Чёрч разработал более слабую систему, просто типизированное лямбда-исчисление , которое расширило лямбда-исчисление, присвоив тип данных всем термам. [40] Это формирует основу для статически типизированного функционального программирования.
Первый высокоуровневый функциональный язык программирования, Lisp , был разработан в конце 1950-х годов для серии научных компьютеров IBM 700/7000 Джоном Маккарти во время работы в Массачусетском технологическом институте (MIT). [41] Функции Lisp были определены с использованием лямбда-нотации Чёрча, расширенной конструкцией меток для разрешения рекурсивных функций. [42] Lisp первым представил множество парадигматических особенностей функционального программирования, хотя ранние Lisp были языками с несколькими парадигмами и включали поддержку многочисленных стилей программирования по мере развития новых парадигм. Более поздние диалекты, такие как Scheme и Clojure , и ответвления, такие как Dylan и Julia , стремились упростить и рационализировать Lisp вокруг чисто функционального ядра, в то время как Common Lisp был разработан для сохранения и обновления парадигматических особенностей многочисленных старых диалектов, которые он заменил. [43]
Information Processing Language (IPL), 1956, иногда упоминается как первый компьютерный функциональный язык программирования. [44] Это язык в стиле ассемблера для манипулирования списками символов. В нем есть понятие генератора , что равнозначно функции, которая принимает функцию в качестве аргумента, и, поскольку это язык уровня ассемблера, код может быть данными, поэтому IPL можно рассматривать как язык, имеющий функции более высокого порядка. Однако он в значительной степени опирается на структуру мутирующего списка и аналогичные императивные функции.
Кеннет Э. Айверсон разработал APL в начале 1960-х годов, описал его в своей книге 1962 года «Язык программирования» ( ISBN 9780471430148 ). APL оказал основное влияние на FP Джона Бэкуса . В начале 1990-х Айверсон и Роджер Хуэй создали J. В середине 1990-х Артур Уитни , ранее работавший с Айверсоном, создал K , который используется в коммерческих целях в финансовых отраслях вместе со своим потомком Q.
В середине 1960-х годов Питер Ландин изобрел машину SECD [45] , первую абстрактную машину для функционального языка программирования, [46] описал соответствие между АЛГОЛом 60 и лямбда-исчислением [ 47 ] [48] и предложил язык программирования ISWIM . [49]
Джон Бэкус представил ФП в своей лекции на вручении премии Тьюринга 1977 года «Можно ли освободить программирование от стиля фон Неймана ? Функциональный стиль и его алгебра программ». [50] Он определяет функциональные программы как построенные иерархическим образом посредством «комбинирования форм», которые допускают «алгебру программ»; на современном языке это означает, что функциональные программы следуют принципу композиционности . [ необходима ссылка ] Статья Бэкуса популяризировала исследования в области функционального программирования, хотя в ней подчеркивалось программирование на уровне функций , а не стиль лямбда-исчисления, который сейчас ассоциируется с функциональным программированием.
Язык ML был создан в 1973 году Робином Милнером в Эдинбургском университете , а Дэвид Тернер разработал язык SASL в Университете Сент-Эндрюс . Также в Эдинбурге в 1970-х годах Берстолл и Дарлингтон разработали функциональный язык NPL . [51] NPL был основан на уравнениях рекурсии Клини и впервые был представлен в их работе по преобразованию программ. [52] Затем Берстолл, Маккуин и Саннелла включили полиморфную проверку типов из ML, чтобы создать язык Hope . [53] ML в конечном итоге развился в несколько диалектов, наиболее распространенными из которых сейчас являются OCaml и Standard ML .
В 1970-х годах Гай Л. Стил и Джеральд Джей Сассман разработали Scheme , как описано в Lambda Papers и учебнике 1985 года Structure and Interpretation of Computer Programs . Scheme был первым диалектом LISP, который использовал лексическую область видимости и требовал оптимизации хвостовых вызовов , функций, которые поощряют функциональное программирование.
В 1980-х годах Пер Мартин-Лёф разработал интуиционистскую теорию типов (также называемую конструктивной теорией типов), которая связывала функциональные программы с конструктивными доказательствами, выраженными как зависимые типы . Это привело к новым подходам к интерактивному доказательству теорем и повлияло на развитие последующих функциональных языков программирования. [ необходима цитата ]
Ленивый функциональный язык Miranda , разработанный Дэвидом Тернером, изначально появился в 1985 году и оказал сильное влияние на Haskell . Поскольку Miranda был проприетарным, Haskell начал с консенсуса в 1987 году, чтобы сформировать открытый стандарт для исследований функционального программирования; выпуски реализаций продолжаются с 1990 года.
Совсем недавно он нашел применение в таких нишах, как параметрическое САПР в языке OpenSCAD , построенном на основе фреймворка CGAL , хотя его ограничение на переназначение значений (все значения рассматриваются как константы) привело к путанице среди пользователей, незнакомых с концепцией функционального программирования. [54]
Функциональное программирование продолжает использоваться в коммерческих целях. [55] [56] [57]
Ряд концепций [58] и парадигм специфичны для функционального программирования и, как правило, чужды императивному программированию (включая объектно-ориентированное программирование ). Однако языки программирования часто обслуживают несколько парадигм программирования, поэтому программисты, использующие «в основном императивные» языки, могли использовать некоторые из этих концепций. [59]
Функции высшего порядка — это функции, которые могут принимать другие функции в качестве аргументов или возвращать их в качестве результатов. В исчислении примером функции высшего порядка является дифференциальный оператор , который возвращает производную функции .
Функции высшего порядка тесно связаны с функциями первого класса в том, что функции высшего порядка и функции первого класса допускают функции в качестве аргументов и результатов других функций. Различие между ними тонкое: «высший порядок» описывает математическую концепцию функций, которые работают с другими функциями, в то время как «первый класс» — это термин из компьютерной науки для сущностей языка программирования, которые не имеют ограничений на их использование (таким образом, функции первого класса могут появляться в любом месте программы, где могут появляться другие сущности первого класса, такие как числа, в том числе в качестве аргументов других функций и в качестве их возвращаемых значений).
Функции высшего порядка позволяют частичное применение или каррирование , технику, которая применяет функцию к ее аргументам по одному за раз, причем каждое применение возвращает новую функцию, которая принимает следующий аргумент. Это позволяет программисту лаконично выразить, например, функцию-последователя как оператор сложения, частично примененный к натуральному числу один.
Чистые функции (или выражения) не имеют побочных эффектов (памяти или ввода-вывода). Это означает, что чистые функции имеют несколько полезных свойств, многие из которых могут быть использованы для оптимизации кода:
Хотя большинство компиляторов для императивных языков программирования обнаруживают чистые функции и выполняют устранение общих подвыражений для вызовов чистых функций, они не всегда могут сделать это для предварительно скомпилированных библиотек, которые обычно не раскрывают эту информацию, тем самым предотвращая оптимизации, включающие эти внешние функции. Некоторые компиляторы, такие как gcc , добавляют дополнительные ключевые слова для программиста, чтобы явно пометить внешние функции как чистые, чтобы включить такие оптимизации. Fortran 95 также позволяет функциям быть обозначенными как чистые . [60] C++11 добавил constexpr
ключевое слово с похожей семантикой.
Итерация (циклирование) в функциональных языках обычно выполняется с помощью рекурсии . Рекурсивные функции вызывают сами себя, позволяя операции повторяться до тех пор, пока она не достигнет базового случая . В общем случае рекурсия требует поддержания стека , который потребляет пространство в линейной степени к глубине рекурсии. Это может сделать рекурсию непозволительно дорогой для использования вместо императивных циклов. Однако специальная форма рекурсии, известная как хвостовая рекурсия, может быть распознана и оптимизирована компилятором в тот же код, который используется для реализации итерации в императивных языках. Оптимизация хвостовой рекурсии может быть реализована путем преобразования программы в стиль передачи продолжения во время компиляции, среди других подходов.
Стандарт языка Scheme требует , чтобы реализации поддерживали правильную хвостовую рекурсию, то есть они должны допускать неограниченное количество активных хвостовых вызовов. [61] [62] Правильная хвостовая рекурсия — это не просто оптимизация; это языковая функция, которая гарантирует пользователям, что они могут использовать рекурсию для выражения цикла, и делать это будет безопасно для пространства. [63] Более того, вопреки своему названию, она учитывает все хвостовые вызовы, а не только хвостовую рекурсию. Хотя правильная хвостовая рекурсия обычно реализуется путем превращения кода в императивные циклы, реализации могут реализовывать ее другими способами. Например, Chicken намеренно поддерживает стек и позволяет стеку переполняться . Однако, когда это происходит, его сборщик мусора забирает пространство обратно, [64] позволяя неограниченное количество активных хвостовых вызовов, даже если он не превращает хвостовую рекурсию в цикл.
Общие шаблоны рекурсии могут быть абстрагированы с помощью функций более высокого порядка, причем катаморфизмы и анаморфизмы (или «складки» и «развертки») являются наиболее очевидными примерами. Такие схемы рекурсии играют роль, аналогичную встроенным управляющим структурам, таким как циклы в императивных языках .
Большинство языков функционального программирования общего назначения допускают неограниченную рекурсию и являются полными по Тьюрингу , что делает проблему остановки неразрешимой , может привести к несостоятельности эквациональных рассуждений и, как правило, требует введения несоответствия в логику, выраженную системой типов языка . Некоторые языки специального назначения, такие как Coq, допускают только обоснованную рекурсию и являются строго нормализующими (незавершающиеся вычисления могут быть выражены только бесконечными потоками значений, называемыми codata ). Как следствие, эти языки не являются полными по Тьюрингу, и выражение определенных функций в них невозможно, но они все еще могут выражать широкий класс интересных вычислений, избегая при этом проблем, вызванных неограниченной рекурсией. Функциональное программирование, ограниченное обоснованной рекурсией с несколькими другими ограничениями, называется полным функциональным программированием . [65]
Функциональные языки можно классифицировать по тому, используют ли они строгую (жадную) или нестрогую (ленивую) оценку, концепции, которые относятся к тому, как аргументы функции обрабатываются при оценке выражения. Техническое различие заключается в денотационном семантике выражений, содержащих неудачные или расходящиеся вычисления. При строгой оценке оценка любого термина, содержащего неудачный подтерм, терпит неудачу. Например, выражение:
длина печати([2+1, 3*2, 1/0, 5-4])
терпит неудачу при строгой оценке из-за деления на ноль в третьем элементе списка. При ленивой оценке функция length возвращает значение 4 (т. е. количество элементов в списке), поскольку ее оценка не пытается оценить элементы, составляющие список. Короче говоря, строгая оценка всегда полностью оценивает аргументы функции перед вызовом функции. Ленивая оценка не оценивает аргументы функции, если только их значения не требуются для оценки самого вызова функции.
Обычной стратегией реализации ленивых вычислений в функциональных языках является редукция графа . [66] Ленивые вычисления используются по умолчанию в нескольких чистых функциональных языках, включая Miranda , Clean и Haskell .
Hughes 1984 выступает за ленивую оценку как механизм улучшения модульности программы посредством разделения интересов , облегчая независимую реализацию производителей и потребителей потоков данных. [2] Launchbury 1993 описывает некоторые трудности, которые вносит ленивая оценка, особенно при анализе требований к хранению программы, и предлагает операционную семантику для помощи в таком анализе. [67] Harper 2009 предлагает включить как строгую, так и ленивую оценку в один и тот же язык, используя систему типов языка для их различения. [68]
Особенно с момента разработки вывода типов Хиндли–Милнера в 1970-х годах функциональные языки программирования имели тенденцию использовать типизированное лямбда-исчисление , отклоняя все недействительные программы во время компиляции и рискуя ложноположительными ошибками , в отличие от нетипизированного лямбда-исчисления , которое принимает все действительные программы во время компиляции и рискует ложноотрицательными ошибками , используемого в Lisp и его вариантах (таких как Scheme ), поскольку они отклоняют все недействительные программы во время выполнения, когда информации достаточно, чтобы не отклонять действительные программы. Использование алгебраических типов данных делает манипуляцию сложными структурами данных удобной; наличие строгой проверки типов во время компиляции делает программы более надежными в отсутствие других методов обеспечения надежности, таких как разработка через тестирование , в то время как вывод типов освобождает программиста от необходимости вручную объявлять типы компилятору в большинстве случаев.
Некоторые ориентированные на исследования функциональные языки, такие как Coq , Agda , Cayenne и Epigram, основаны на интуиционистской теории типов , которая позволяет типам зависеть от терминов. Такие типы называются зависимыми типами . Эти системы типов не имеют разрешимого вывода типов и их трудно понимать и программировать с ними. [69] [70] [71] [72] Но зависимые типы могут выражать произвольные предложения в логике высшего порядка . Таким образом, благодаря изоморфизму Карри–Ховарда , хорошо типизированные программы на этих языках становятся средством написания формальных математических доказательств , из которых компилятор может генерировать сертифицированный код . Хотя эти языки в основном представляют интерес для академических исследований (включая формализованную математику ), они начали использоваться и в инженерии. Compcert — это компилятор для подмножества языка C , который написан на Coq и формально проверен. [73]
Ограниченная форма зависимых типов, называемая обобщенными алгебраическими типами данных (GADT), может быть реализована таким образом, чтобы обеспечить некоторые преимущества зависимо типизированного программирования, избежав при этом большинства его неудобств. [74] GADT доступны в компиляторе Glasgow Haskell , в OCaml [75] и в Scala [ 76] и были предложены в качестве дополнений к другим языкам, включая Java и C#. [77]
Функциональные программы не имеют операторов присваивания, то есть значение переменной в функциональной программе никогда не меняется после определения. Это исключает любые шансы побочных эффектов, поскольку любая переменная может быть заменена ее фактическим значением в любой точке выполнения. Таким образом, функциональные программы ссылочно прозрачны. [78]
Рассмотрим оператор присваивания Cx=x*10
, он изменяет значение, присвоенное переменной x
. Допустим, что начальное значение x
было 1
, тогда два последовательных вычисления переменной x
дают 10
и 100
соответственно. Очевидно, что замена x=x*10
на 10
или 100
придает программе другой смысл, и поэтому выражение не является ссылочно прозрачным. Фактически, операторы присваивания никогда не являются ссылочно прозрачными.
Теперь рассмотрим другую функцию, например, прозрачную , поскольку она не изменяет неявно входной x и, таким образом, не имеет таких побочных эффектов . Функциональные программы используют исключительно этот тип функции и, следовательно, являются ссылочно прозрачными.int plusone(int x) {return x+1;}
Чисто функциональные структуры данных часто представляются иначе, чем их императивные аналоги. [79] Например, массив с постоянным временем доступа и обновления является базовым компонентом большинства императивных языков, и многие императивные структуры данных, такие как хэш-таблица и двоичная куча , основаны на массивах. Массивы могут быть заменены картами или списками случайного доступа, которые допускают чисто функциональную реализацию, но имеют логарифмическое время доступа и обновления. Чисто функциональные структуры данных обладают персистентностью , свойством сохранения предыдущих версий структуры данных немодифицированными. В Clojure постоянные структуры данных используются как функциональные альтернативы их императивным аналогам. Например, постоянные векторы используют деревья для частичного обновления. Вызов метода вставки приведет к созданию некоторых, но не всех узлов. [80]
Функциональное программирование сильно отличается от императивного программирования . Наиболее существенные различия проистекают из того факта, что функциональное программирование избегает побочных эффектов , которые используются в императивном программировании для реализации состояния и ввода-вывода. Чистое функциональное программирование полностью предотвращает побочные эффекты и обеспечивает ссылочную прозрачность.
Функции высшего порядка редко используются в старом императивном программировании. Традиционная императивная программа может использовать цикл для обхода и изменения списка. Функциональная программа, с другой стороны, вероятно, будет использовать функцию высшего порядка "map", которая принимает функцию и список, генерируя и возвращая новый список, применяя функцию к каждому элементу списка.
Следующие два примера (написанные на JavaScript ) достигают того же эффекта: они умножают все четные числа в массиве на 10 и складывают их все, сохраняя конечную сумму в переменной «result».
Традиционный императивный цикл:
const numList = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] ; пусть результат = 0 ; для ( пусть i = 0 ; i < numList.length ; i ++ ) { if ( numList [ i ] % 2 === 0 ) { result + = numList [ i ] * 10 ; } }
Функциональное программирование с функциями высшего порядка:
const result = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] .filter ( n => n % 2 === 0 ) .map ( a = > a * 10 ) .reduce (( a , b ) = > a + b , 0 ) ;
Иногда абстракции, предлагаемые функциональным программированием, могут привести к разработке более надежного кода, который позволяет избежать определенных проблем, которые могут возникнуть при построении на основе большого объема сложного, императивного кода, например, ошибок, связанных с несоответствием одному значению (см. десятое правило Гринспена ).
Есть задачи (например, поддержание баланса банковского счета), которые часто кажутся наиболее естественно реализованными с помощью состояния. Чистое функциональное программирование выполняет эти задачи, а также задачи ввода-вывода, такие как прием пользовательского ввода и вывод на экран, другим способом.
Чистый функциональный язык программирования Haskell реализует их с помощью монад , полученных из теории категорий . [81] Монады предлагают способ абстрагирования определенных типов вычислительных шаблонов, включая (но не ограничиваясь) моделирование вычислений с изменчивым состоянием (и другими побочными эффектами, такими как ввод-вывод) императивным образом без потери чистоты. Хотя существующие монады могут быть легко применены в программе, при наличии соответствующих шаблонов и примеров, многие студенты считают их сложными для концептуального понимания, например, когда их просят определить новые монады (что иногда необходимо для определенных типов библиотек). [82]
Функциональные языки также имитируют состояния, передавая неизменяемые состояния. Это можно сделать, заставив функцию принимать состояние как один из своих параметров и возвращать новое состояние вместе с результатом, оставляя старое состояние неизменным. [83]
Нечистые функциональные языки обычно включают более прямой метод управления изменчивым состоянием. Clojure , например, использует управляемые ссылки, которые можно обновлять, применяя чистые функции к текущему состоянию. Такой подход обеспечивает изменчивость, при этом по-прежнему поощряя использование чистых функций как предпочтительного способа выражения вычислений. [ необходима цитата ]
Альтернативные методы, такие как логика Хоара и уникальность, были разработаны для отслеживания побочных эффектов в программах. Некоторые современные исследовательские языки используют системы эффектов , чтобы сделать наличие побочных эффектов явным. [ необходима цитата ]
Функциональные языки программирования обычно менее эффективны в использовании ЦП и памяти, чем императивные языки, такие как C и Pascal . [84] Это связано с тем, что некоторые изменяемые структуры данных, такие как массивы, имеют очень простую реализацию с использованием существующего оборудования. К плоским массивам можно очень эффективно обращаться с помощью глубоко конвейеризированных ЦП, эффективно предварительно выбирать их через кэши (без сложного отслеживания указателей) или обрабатывать с помощью инструкций SIMD. Также нелегко создать их столь же эффективные универсальные неизменяемые аналоги. Для чисто функциональных языков наихудшее замедление логарифмически по количеству используемых ячеек памяти, поскольку изменяемая память может быть представлена чисто функциональной структурой данных с логарифмическим временем доступа (например, сбалансированным деревом). [85] Однако такие замедления не являются универсальными. Для программ, которые выполняют интенсивные числовые вычисления, функциональные языки, такие как OCaml и Clean , лишь немного медленнее, чем C, согласно The Computer Language Benchmarks Game . [86] Для программ, обрабатывающих большие матрицы и многомерные базы данных , были разработаны языки программирования для работы с массивами (такие как J и K ) с оптимизацией скорости.
Неизменяемость данных во многих случаях может привести к эффективности выполнения, позволяя компилятору делать предположения, которые небезопасны в императивном языке, тем самым увеличивая возможности для встроенного расширения . [87] Даже если задействованное копирование, которое может показаться неявным при работе с постоянными неизменяемыми структурами данных, может показаться вычислительно затратным, некоторые функциональные языки программирования, такие как Clojure , решают эту проблему, реализуя механизмы для безопасного разделения памяти между формально неизменяемыми данными. [88] Rust отличается своим подходом к неизменяемости данных, который включает неизменяемые ссылки [89] и концепцию, называемую временем жизни. [90]
Неизменяемые данные с разделением идентичности и состояния и схемы без общего доступа также потенциально могут быть более подходящими для параллельного и конкурентного программирования за счет снижения или устранения риска определенных опасностей параллелизма, поскольку параллельные операции обычно являются атомарными , и это позволяет устранить необходимость в блокировках. Так, например, java.util.concurrent
реализуются классы, некоторые из которых являются неизменяемыми вариантами соответствующих классов, которые не подходят для параллельного использования. [91] Функциональные языки программирования часто имеют модель параллелизма, которая вместо общего состояния и синхронизации использует механизмы передачи сообщений (например, модель акторов , где каждый актор является контейнером для состояния, поведения, дочерних акторов и очереди сообщений). [92] [93] Этот подход распространен в Erlang / Elixir или Akka .
Ленивая оценка также может ускорить программу, даже асимптотически, тогда как она может замедлить ее максимум на постоянный множитель (однако, она может привести к утечкам памяти, если используется неправильно). Launchbury 1993 [67] обсуждает теоретические вопросы, связанные с утечками памяти из-за ленивой оценки, а O'Sullivan et al. 2008 [94] дает некоторые практические советы по их анализу и исправлению. Однако наиболее общие реализации ленивой оценки, широко использующие разыменованный код и данные, плохо работают на современных процессорах с глубокими конвейерами и многоуровневыми кэшами (где промах кэша может стоить сотни циклов) [ требуется ссылка ] .
Некоторые функциональные языки программирования могут не оптимизировать абстракции, такие как функции более высокого порядка, такие как " map " или " filter ", так же эффективно, как базовые императивные операции. Рассмотрим в качестве примера следующие два способа проверки, является ли 5 четным числом в Clojure :
( четно? 5 ) ( .равно ( mod 5 2 ) 0 )
При тестировании с использованием инструмента Criterium на ПК Ryzen 7900X GNU/Linux в Leiningen REPL 2.11.2, работающем на Java VM версии 22 и Clojure версии 1.11.1, первая реализация, которая реализована как:
( defn even? "Возвращает true, если n четное, выдает исключение, если n не является целым числом" { :added "1.0" :static true } [ n ] ( if ( integer? n ) ( zero? ( bit-and ( clojure.lang.RT/uncheckedLongCast n ) 1 )) ( throw ( IllegalArgumentException. ( str "Аргумент должен быть целым числом: " n )))))
имеет среднее время выполнения 4,76 мс, в то время как второй, в котором .equals
происходит прямой вызов базового метода Java , имеет среднее время выполнения 2,8 мкс — примерно в 1700 раз быстрее. Частично это можно отнести к проверке типов и обработке исключений, задействованных в реализации even?
, поэтому давайте возьмем, к примеру, библиотеку lo для Go , которая реализует различные функции высшего порядка, распространенные в функциональных языках программирования, с использованием дженериков . В тесте, предоставленном автором библиотеки, вызов map
на 4% медленнее, чем эквивалентный for
цикл, и имеет тот же профиль распределения , [95] что можно отнести к различным оптимизациям компилятора, таким как встраивание . [96]
Одной из отличительных особенностей Rust являются абстракции с нулевой стоимостью . Это означает, что их использование не налагает дополнительных накладных расходов во время выполнения. Это достигается благодаря компилятору, использующему разворачивание цикла , где каждая итерация цикла, будь то императивная или использующая итераторы, преобразуется в отдельную инструкцию ассемблера , без накладных расходов на код управления циклом. Если итеративная операция записывает в массив, элементы результирующего массива будут сохранены в определенных регистрах ЦП , что обеспечивает постоянный доступ во время выполнения. [97]
Можно использовать функциональный стиль программирования в языках, которые традиционно не считаются функциональными языками. [98] Например, и D [99] , и Fortran 95 [60] явно поддерживают чистые функции.
JavaScript , Lua , [100] Python и Go [101] имели функции первого класса с самого начала. [102] Python поддерживал « lambda », « map », « reduce » и « filter » в 1994 году, а также замыкания в Python 2.2, [103] хотя Python 3 перенес «reduce» в functools
стандартный библиотечный модуль. [104] Функции первого класса были введены в другие основные языки, такие как PHP 5.3, Visual Basic 9 , C# 3.0, C++11 и Kotlin . [28] [ требуется ссылка ]
В PHP полностью поддерживаются анонимные классы , замыкания и лямбды. Разрабатываются библиотеки и расширения языка для неизменяемых структур данных, чтобы помочь программированию в функциональном стиле.
В Java анонимные классы иногда могут использоваться для имитации замыканий; [105] однако анонимные классы не всегда являются подходящей заменой замыканий, поскольку они имеют более ограниченные возможности. [106] Java 8 поддерживает лямбда-выражения в качестве замены для некоторых анонимных классов. [107]
В C# анонимные классы не нужны, поскольку замыкания и лямбды полностью поддерживаются. Библиотеки и расширения языка для неизменяемых структур данных разрабатываются для помощи в программировании в функциональном стиле в C#.
Многие объектно-ориентированные шаблоны проектирования можно выразить в терминах функционального программирования: например, шаблон стратегии просто диктует использование функции более высокого порядка, а шаблон посетителя примерно соответствует катаморфизму или свертке .
Аналогично, идея неизменяемых данных из функционального программирования часто включается в императивные языки программирования, [108] например, кортеж в Python, который является неизменяемым массивом, и Object.freeze() в JavaScript. [109]
Логическое программирование можно рассматривать как обобщение функционального программирования, в котором функции являются частным случаем отношений. [110] Например, функция, mother(X) = Y, (каждый X имеет только одну мать Y) может быть представлена отношением mother(X, Y). В то время как функции имеют строгий шаблон ввода-вывода аргументов, отношения могут быть запрошены с любым шаблоном входов и выходов. Рассмотрим следующую логическую программу:
Мать ( Чарльз , Элизабет ). Мать ( Гарри , Диана ).
Программу можно запросить, как и функциональную программу, для генерации матерей из детей:
?- мать ( harry , X ). X = diana . ?- мать ( charles , X ). X = elizabeth .
Но его также можно запросить в обратном порядке , чтобы сгенерировать дочерние элементы:
?- мать ( X , Элизабет ). X = Чарльз . ?- мать ( X , Диана ). X = Гарри .
Его даже можно использовать для генерации всех экземпляров материнского отношения:
?- мать ( X , Y ). X = Чарльз , Y = Элизабет . X = Гарри , Y = Диана .
По сравнению с реляционным синтаксисом, функциональный синтаксис является более компактной записью для вложенных функций. Например, определение бабушки по материнской линии в функциональном синтаксисе можно записать во вложенной форме:
материнская_бабушка ( X ) = мать ( мать ( X )).
То же самое определение в реляционной нотации необходимо записать в невложенной форме:
maternal_grandmother ( X , Y ) :- мать ( X , Z ), мать ( Z , Y ).
Здесь :-
означает, если и ,
означает и .
Однако разница между этими двумя представлениями просто синтаксическая. В Ciao Prolog отношения могут быть вложенными, как функции в функциональном программировании: [111]
дедушка ( X ) := родитель ( родитель ( X )). родитель ( X ) := мать ( X ). родитель ( X ) := отец ( X ).мать ( Чарльз ) := Элизабет . отец ( Чарльз ) := Филлип . мать ( Гарри ) := Диана . отец ( Гарри ) := Чарльз .?- дедушка ( X , Y ). X = Гарри , Y = Элизабет . X = Гарри , Y = Филлип .
Ciao преобразует функциональную нотацию в реляционную форму и выполняет полученную логическую программу, используя стандартную стратегию выполнения Prolog.
Emacs , семейство текстовых редакторов с высокой степенью расширения, использует собственный диалект Lisp для написания плагинов. Оригинальный автор самой популярной реализации Emacs, GNU Emacs и Emacs Lisp, Ричард Столлман считает Lisp одним из своих любимых языков программирования. [112]
Helix, начиная с версии 24.03, поддерживает предварительный просмотр AST в виде S-выражений , которые также являются основной функцией семейства языков программирования Lisp. [113]
Электронные таблицы можно считать формой чистой, нулевого порядка , строгой оценки функциональной системы программирования. [114] Однако в электронных таблицах обычно отсутствуют функции высшего порядка, а также повторное использование кода, а в некоторых реализациях также отсутствует рекурсия. Было разработано несколько расширений для программ электронных таблиц, чтобы включить функции высшего порядка и повторно используемые функции, но до сих пор они остаются в основном академическими по своей природе. [115]
Функциональное программирование является активной областью исследований в области теории языков программирования . Существует несколько рецензируемых изданий, посвященных функциональному программированию, включая Международную конференцию по функциональному программированию , Журнал функционального программирования и Симпозиум по тенденциям в функциональном программировании .
Функциональное программирование использовалось в широком спектре промышленных приложений. Например, Erlang , разработанный шведской компанией Ericsson в конце 1980-х годов, изначально использовался для реализации отказоустойчивых телекоммуникационных систем, [11] но с тех пор стал популярным для создания ряда приложений в таких компаниях, как Nortel , Facebook , Électricité de France и WhatsApp . [10] [12] [116] [ 117] [118] Scheme , диалект Lisp , использовался в качестве основы для нескольких приложений на ранних компьютерах Apple Macintosh [3] [4] и применялся к таким проблемам, как программное обеспечение для обучения и моделирования [5] и управление телескопом . [6] OCaml , представленный в середине 1990-х годов, нашел коммерческое применение в таких областях, как финансовый анализ, [14] проверка драйверов , программирование промышленных роботов и статический анализ встроенного программного обеспечения . [15] Haskell , хотя изначально и задумывался как исследовательский язык, [17] также применялся в таких областях, как аэрокосмические системы, проектирование аппаратного обеспечения и веб-программирование. [16] [17]
Другие функциональные языки программирования, которые использовались в промышленности, включают Scala , [119] F# , [18] [19] Wolfram Language , [7] Lisp , [120] Standard ML [121] [122] и Clojure. [123] Scala широко используется в науке о данных , [124] в то время как ClojureScript , [125] Elm [126] или PureScript [127] являются некоторыми из функциональных языков программирования фронтенда, используемых в производстве. Фреймворк Phoenix от Elixir также используется некоторыми относительно популярными коммерческими проектами, такими как Font Awesome или Allegro (одна из крупнейших платформ электронной коммерции в Польше) [128] , рекламная платформа Allegro Lokalnie. [129]
Функциональные «платформы» были популярны в финансах для аналитики рисков (особенно в крупных инвестиционных банках). Факторы риска кодируются как функции, которые формируют взаимозависимые графики (категории) для измерения корреляций в рыночных сдвигах, аналогично оптимизации базиса Грёбнера , но также и для нормативных рамок, таких как Comprehensive Capital Analysis and Review . Учитывая использование вариаций OCaml и Caml в финансах, эти системы иногда считаются связанными с категориальной абстрактной машиной . Функциональное программирование находится под сильным влиянием теории категорий . [ требуется цитата ]
Во многих университетах преподают функциональное программирование. [130] [131] [132] [133] Некоторые рассматривают его как вводную концепцию программирования [133], в то время как другие сначала обучают методам императивного программирования. [132] [134]
За пределами компьютерной науки функциональное программирование используется для обучения решению задач, алгебраическим и геометрическим концепциям. [135] Оно также использовалось для обучения классической механике, как в книге «Структура и интерпретация классической механики» .
В частности, Scheme уже много лет является относительно популярным выбором для обучения программированию. [136] [137]
{{cite journal}}
: CS1 maint: DOI неактивен по состоянию на ноябрь 2024 г. ( ссылка )Haskell имеет широкий спектр коммерческого использования: от аэрокосмической и оборонной промышленности до финансов, веб-стартапов, фирм по проектированию оборудования и производителей газонокосилок.
Effective Scala.
Метод Object.freeze() замораживает объект. Замороженный объект больше не может быть изменен; замораживание объекта предотвращает добавление к нему новых свойств, удаление существующих свойств, предотвращает изменение перечисляемости, настраиваемости или возможности записи существующих свойств и предотвращает изменение значений существующих свойств. Кроме того, замораживание объекта также предотвращает изменение его прототипа. freeze() возвращает тот же объект, который был передан.