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

18

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

Эндрю Мартин
источник
4
Первым шагом, вероятно, является осознание того, что проблемы по своей сути не являются состоянием.
Теластин
4
Некоторые проблемы являются неотъемлемыми, например, запись в базу данных или создание графического интерфейса. Если взять мой пример с имитатором частиц, какой бы альтернативный способ думать об этом? Возвращение новых частиц каждый раз, когда их положение обновляется, чтобы избежать состояния, кажется мне неэффективным, и не является хорошей моделью реального мира.
Эндрю Мартин
4
За исключением, возможно, примера базы данных, эти проблемы по своей сути не являются состоянием. Например, для программирования GUI вы действительно используете изменяемое состояние как плохую, неявную модель времени ; функциональное реактивное программирование позволяет вам явно моделировать время, не полагаясь на состояние, предоставляя потоки событий, которые вы можете комбинировать.
Тихон Джелвис
1
Существует более простое решение: когда вы сталкиваетесь с проблемой, которую нелегко моделировать с помощью методов FP, не используйте функциональное программирование для ее решения. Правильный инструмент для работы и все такое ...
Мейсон Уилер
1
@AndrewMartin Не очень хорошая модель реального мира? Математика, используемая в физике для моделирования реального мира, является чисто функциональной. С хорошим сборщиком мусора выделение объекта так же дешево, как столкновение с указателем, а время сбора пропорционально количеству живых объектов. Во всяком случае, держу пари, что основным источником неэффективности в функциональном программировании является использование структур данных, которые неэффективны для кэширования. Связанные списки и бинарные деревья не являются точным предиктором эффективности кеша.
Доваль

Ответы:

20

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

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

Карл Билефельдт
источник
5
+1 Это нормально иметь состояние в FP, только не изменяемое состояние.
Jhewlett
1
Спасибо за это понимание. Мои опасения по поводу неэффективности были сорваны @logc; технические детали того, как преобразуется состояние, являются проблемой реализации низкого уровня, которую должен решать сам язык. Я видел, как Рич Хики объясняет, как он делает это с Clojure в видео.
Эндрю Мартин
1
@jhewlett: Точнее: FP имеет состояние, даже изменяемое, но они не представляют его с помощью изменяемых переменных.
Джорджио
9

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

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

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

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

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

logc
источник
4
Я думаю, что фрагмент об обновлении большого списка вводит в заблуждение; Я не знаю ни одного компилятора, который действительно выполнил бы эту оптимизацию для вас. Даже если компилятор может это сделать, это возможно только в тех случаях, когда вы не придерживаетесь предыдущих версий списка. Реальным решением является использование структуры списка данных , которая не требует копирования все , что нужно изменить один элемент.
Доваль
@Doval: «Даже если компилятор может это сделать, это возможно только в тех случаях, когда вы не держитесь за предыдущие версии списка.»: Это напоминает мне об уникальных типах в Clean.
Джорджио
4

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

Rich Hickey освещает эти идеи:

Есть и другие разговоры, но это должно направить вас в правильном направлении.

Марио Т. Ланца
источник
3

При написании больших и умеренно больших приложений я часто находил полезным различать разделы приложения с состоянием и от состояния без состояния.

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

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

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

Р Саху
источник
Как это отвечает на вопрос? (не отрицательное голосование)
Кравемир
@kravemir, независимо от того, пишете ли вы приложение с использованием ООП или FP, вы должны понимать, где находится состояние приложения.
R Sahu