Асинхронное программирование на функциональных языках


31

Я в основном программист на C / C ++, а это значит, что большая часть моего опыта связана с процедурными и объектно-ориентированными парадигмами. Однако, как известно многим программистам на C ++, C ++ с годами сместил акцент на стиль функционального esque, завершившийся, наконец, добавлением лямбд и закрытий в C ++ 0x.

Несмотря на это, хотя у меня есть значительный опыт написания кода в функциональном стиле с использованием C ++, у меня очень мало опыта работы с реальными функциональными языками, такими как Lisp, Haskell и т. Д.

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

Однако, исходя из опыта C ++, я не понимаю, как эта философия «без побочных эффектов» работает с асинхронным программированием. Под асинхронным программированием я подразумеваю любой фреймворк / API / стиль кодирования, который отправляет предоставляемые пользователем обработчики событий для обработки событий, которые происходят асинхронно (вне потока программы). Это включает в себя асинхронные библиотеки, такие как Boost.ASIO, или даже просто старый C обработчики сигналов или обработчики событий Java GUI.

Единственное, что у них общего, - то, что природа асинхронного программирования, по-видимому, требует создания побочных эффектов (состояния), чтобы основной поток программы осознавал, что обработчик асинхронного события был вызван. Как правило, в среде, подобной Boost.ASIO, обработчик событий изменяет состояние объекта, так что эффект события распространяется за пределы срока службы функции обработчика событий. Действительно, что еще может сделать обработчик событий? Он не может «вернуть» значение точке вызова, потому что нет точки вызова. Обработчик событий не является частью основного потока программы, поэтому единственный способ, которым он может оказать какое-либо влияние на реальную программу, - это изменить какое-то состояние (или другое состояние longjmpна другую точку выполнения).

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


3
Ух, я как раз собирался написать вопрос, нравится это и не знал, как его поставить, а потом увидел это в предложениях!
Амог Талпалликар

Ответы:


11

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

На самом деле, вы должны изменить состояние приложения, чтобы сделать что-нибудь. Ребята из Haskell скажут вам, что их программы «чистые», потому что они обертывают все свои изменения состояния в монаду. Однако их программы все еще взаимодействуют с внешним миром. (Иначе какой смысл!)

Функциональное программирование подчеркивает «отсутствие побочных эффектов», когда это имеет смысл. Однако, чтобы заниматься программированием в реальном мире, как вы сказали, вам нужно изменить состояние мира. (Например, реагирование на события, запись на диск и т. Д.)

Для получения дополнительной информации об асинхронном программировании на функциональных языках я настоятельно призываю вас изучить модель программирования F # Asynchronous Workflows . Это позволяет вам писать функциональные программы, скрывая все грязные детали перехода потоков внутри библиотеки. (В манере, очень похожей на монады в стиле Haskell.)

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


5
Также: глядя на Эрланга помогает. Язык очень простой, чистый (все данные неизменны) и все об асинхронной обработке.
9000

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

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

8

Это увлекательный вопрос. На мой взгляд, наиболее интересным является подход, принятый в Clojure и объясненный в этом видео:

http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey

В основном предлагаемое «решение» заключается в следующем:

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

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


6

Одно замечание: функциональный язык чистый, но его среда выполнения - нет.

Например, среды выполнения Haskell включают очереди, мультиплексирование потоков, сборку мусора и т. Д., Причем все это не чисто.

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


2

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

Это было бы смысл тогда.

Стиль звука без побочных эффектов несовместим с фреймворками, которые зависят от состояния. Найти новую основу.

Например, стандарт Python WSGI позволяет нам создавать приложения без побочных эффектов.

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


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

1

Изучив инкапсуляцию в Borland C ++ после изучения C, когда в Borland C ++ не было шаблонов, позволяющих использовать дженерики, парадигма ориентации на объекты сделала меня неловким. Несколько более естественный способ вычисления - фильтрация данных по каналам. Выходящий поток имел отдельную и независимую идентичность с внутренним неизменным входным потоком, а не был побочным эффектом, т. Е. Каждый источник данных (или фильтр) был автономен от других. Нажатие клавиши (пример события) ограничивало асинхронные комбинации пользовательского ввода доступными кодами клавиш. Функции работают с аргументами входных параметров, а состояние, инкапсулированное классом, является просто ярлыком для избежания явной передачи повторяющихся аргументов между небольшим подмножеством функций, а также для предосторожности в отношении связанного контекста, предотвращающего злоупотребление этими аргументами из любой произвольной функции.

Строгое следование определенной парадигме вызывает неудобство, связанное с утечкой абстракций, например. коммерческие среды выполнения, такие как JRE, DirectX, .net целевые объектно-ориентированные сторонники в первую очередь. Чтобы ограничить неудобства, языки либо выбирают сложные в научном отношении монады, такие как Haskell, либо гибкую поддержку нескольких парадигм, например, F #. Если инкапсуляция не используется в некоторых случаях использования множественного наследования, многопарадигмальный подход может быть лучшей альтернативой некоторым, иногда сложным, специфичным для парадигмы шаблонам программирования.

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