Существует ли неизменность в функциональном программировании?


9

Хотя я работаю программистом в своей повседневной жизни и использую все модные языки (Python, Java, C и т. Д.), У меня все еще нет четкого представления о том, что такое функциональное программирование. Из того, что я прочитал, одно свойство функциональных языков состоит в том, что структуры данных являются неизменяемыми . Для меня одного это вызывает много вопросов. Но сначала я напишу немного того, что я понимаю об неизменности, и если я ошибаюсь, не стесняйтесь поправлять меня.

Мое понимание неизменности:

  • Когда программа запускается, она имеет фиксированные структуры данных с фиксированными данными
  • Нельзя добавлять новые данные в эти структуры
  • В коде нет переменных
  • Вы можете просто «скопировать» из уже данных или в настоящее время рассчитанных данных
  • Из-за всего вышесказанного неизменность добавляет огромную сложность в программу

Мои вопросы:

  1. Если предполагается, что структуры данных остаются такими же (неизменяемыми), как, черт возьми, кто-то добавляет новый элемент в список?
  2. Какой смысл иметь программу, которая не может получить новые данные? Допустим, к вашему компьютеру подключен датчик, который хочет передавать данные в программу. Значит ли это, что мы нигде не можем хранить входящие данные?
  3. Как функциональное программирование хорошо для машинного обучения в этом случае? Поскольку машинное обучение строится из предположения об обновлении «восприятия» программой вещей - таким образом, сохраняются новые данные.

2
Я не согласен с вами, когда вы говорите, что в функциональном коде нет переменных. Существуют переменные в математическом смысле «количество, которое может принимать любое из набора значений». Конечно, они не изменчивы , но и не математики.
Эдуард

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

@jkff Что ты пытаешься сказать? Это Хаскель имеет нефункциональные особенности. Вопрос не в Haskell, а в функциональном программировании. Или вы утверждаете, что все это функционально? Как? Так что должно быть не так с философствованием, как вы говорите. Как абстракция сбивает с толку? ОП вопрос очень разумный.
Бабу

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

Один из способов взглянуть на функциональное программирование - сказать, что это программирование без побочных эффектов. Вы можете сделать это на своем «модном» языке по вашему выбору. Только не избегайте всех переназначений: например, в Java все ваши переменные будут конечными, а все ваши методы будут доступны только для чтения.
reinierpost

Ответы:


10

Когда программа запускается, она имеет фиксированные структуры данных с фиксированными данными

Это немного заблуждение. Он имеет фиксированную форму и фиксированный набор правил перезаписи, но эти правила переписывания могут вылиться во что-то гораздо большее. Например, выражение [1..100000000] в Haskell представлено очень небольшим количеством кода, но его нормальная форма является массивной.

Нельзя добавлять новые данные в эти структуры

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

В коде нет переменных

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

Вы можете просто «скопировать» из уже данных или в настоящее время рассчитанных данных

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

Например, «sum [1..1000]» - это не вычисление, которое я хочу выполнить, но оно довольно легко сделано Haskell. Мы дали ему небольшое выражение, которое имело для нас значение, и Хаскелл выдал нам соответствующее число. Так что он определенно выполняет расчет.

Если предполагается, что структуры данных остаются такими же (неизменяемыми), как, черт возьми, кто-то добавляет новый элемент в список?

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

Какой смысл иметь программу, которая не может получить новые данные? Допустим, к вашему компьютеру подключен датчик, который хочет передавать данные в программу. Значит ли это, что мы нигде не можем хранить входящие данные?

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

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

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


Я чувствую, что вы удобно проигнорировали то, что, возможно, является, пожалуй, самым важным пунктом в посте @ Pithikos, который является проблемой пространства - функциональные программы используют больше места, чем императивные (вы не можете писать алгоритмы на месте и тому подобное)
user541686

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

1
Я думаю, что вы несколько искажаете ML. Да, ввод / вывод может происходить где угодно, но способ введения новой информации в существующие структуры строго контролируется.
dfeuer

@Pithikos, есть переменные повсюду; они просто отличаются от того, к чему вы привыкли, как указал Эдуард. И вещи постоянно распределяются и мусор. Как только вы действительно войдете в функциональное программирование, вы лучше поймете, как оно происходит на самом деле.
dfeuer 25.01.15

1
Это правда, что существуют алгоритмы, которые не имеют чисто функциональной реализации с той же временной сложностью, что и наиболее известная императивная реализация - например, структура данных Union-Find (и, конечно, массивы :)). Я полагаю, есть и такие случаи для пространства сложность. Но это исключения - наиболее распространенные алгоритмы / структуры данных имеют реализации с эквивалентной временной и пространственной сложностью. Это субъективный вопрос стиля программирования и (с постоянным фактором) качества компилятора.
JKFF

4

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

Вычислительный контекст

Это очень хороший вопрос, который представляет собой интересное продолжение (а не дубликат) другого недавнего вопроса: в чем разница между присвоением, оценкой и привязкой имени?

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

Есть несколько вопросов, которые нужно рассмотреть, чтобы ответить вам, в том числе:

  • Что такое модель вычислений и какие концепции имеют смысл для данной модели

  • Каково значение слов, которые вы используете, и как это зависит от контекста

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

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

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

Вычислительные парадигмы

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

Если вы остаетесь на теоретическом уровне, есть много моделей вычислений: машина Тьюринга (TM), машина RAM и другие , лямбда-исчисление, комбинаторная логика, теория рекурсивных функций, системы полуэти и т. Д. Более мощные вычислительные системы модели оказались эквивалентными с точки зрения того, к чему они могут обратиться, и в этом суть тезиса Черча-Тьюринга .

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

β

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

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

Как правило, такие языки, как Haskell и ML или CAML, считаются функциональными, но они допускают императивное поведение ... Иначе зачем говорить о « чисто функциональном подмножестве »?

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

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

Что такое переменная?

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

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

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

Переменные любого вида, а также константы могут быть представлены идентификаторами.

Императивная переменная может изменить свое значение, и это является основой изменчивости. Функциональная переменная не может.

Языки программирования обычно позволяют создавать более крупные сущности из более мелких в языке.

Императивные языки позволяют таким конструкциям включать переменные, и это то, что дает вам изменяемые данные.

Как читать программу

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

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

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

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

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

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

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

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

Вы не копируете значения (это не имеет смысла, это чуждое понятие). Вы просто используете значения, которые существуют в доменах, которые вы определили в вашей программе. Либо вы описываете их (как литералы), либо они являются результатом применения функции к некоторым другим значениям. Вы можете дать им имя (определяя таким образом константу), чтобы убедиться, что одно и то же значение используется в разных местах программы. Обратите внимание, что приложение функции должно восприниматься не как вычисление, а как результат применения к заданным аргументам. Письмо 5+2или письмо 7равняется тому же самому. Что согласуется с предыдущим пунктом.

Здесь нет обязательных переменных. Назначение невозможно. Вы можете привязывать имена только к значениям (для формирования констант), в отличие от императивных языков, где вы можете привязывать имена к назначаемым переменным.

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

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

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

Отвечая на вопросы

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

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

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

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

Заключительные замечания

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

Можно ли писать в чисто аппликативном стиле? Ответ был известен около 40 лет, и это «да». Самой целью денотационной семантики в том виде, в каком она появилась в 1970-х годах, было именно то, чтобы перевести (скомпилировать) языки в чисто функциональный стиль, который считался лучше понятным математически и, таким образом, считался лучшим средством для определения семантики программ.

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


0

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

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

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

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


«Функциональные программы, как и любые другие программы, действительно отображают целые числа в целые». Как, скажем, Minecraft, действительно функция, отображающая целые числа в целые числа?
Дэвид Ричерби

С легкостью. Каждый байт можно интерпретировать как двоичное целое число. Состояние в компьютере - это набор байтов. Программа, даже Minecraft, манипулирует состоянием компьютеров, отображая его из одного состояния в другое.
Джепп Хартмунд

Пользовательский ввод не вписывается в этот мир.
Дэвид Ричерби

Пользовательский ввод является частью состояния компьютера. Это не просто существует на вашем экране.
Джеппе Хартмунд
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.