Почему побочные эффекты в функциональном программировании считаются злом?

softwareengineering.stackexchange https://softwareengineering.stackexchange.com/questions/15269

Вопрос

Я считаю, что побочные эффекты — это естественное явление.Но это что-то вроде табу в функциональных языках.Каковы причины?

Мой вопрос касается стиля функционального программирования.Не все языки/парадигмы программирования.

Это было полезно?

Решение

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

Это также позволяет легко составлять эти функции для создания нового поведения.

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

РЕДАКТИРОВАТЬ: По просьбе Бенджола: потому что большая часть вашего штата хранится в стеке (поток данных, а не поток управления, как назвал Джонас здесь), вы можете параллельно или иным образом переупорядочить выполнение тех частей ваших вычислений, которые не зависят друг от друга. Вы можете легко найти эти независимые части, потому что одна часть не предоставляет входные данные другой.

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

Другие советы

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

На практике приложения должны иметь некоторые побочные эффекты. Саймон Пейтон-Джонс, основной участник функционального языка программирования Haskell, сказал следующее: «В конце концов, любая программа должна манипулировать состоянием. Программа, которая не имеет побочных эффектов,-это своего рода черный ящик. Все, что вы можете сказать, это что коробка становится жарче ". (http://oscon.blip.tv/file/324976) Ключ заключается в том, чтобы ограничить побочные эффекты, четко идентифицировать их и избежать рассеяния их по всему коду.

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

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

Несколько заметок:

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

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

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

Рассмотрим этот простой пример:

val foo = 42
// Several lines of code you don't really care about, but that contain a
// lot of function calls that use foo and may or may not change its value
// by side effect.

// Code you are troubleshooting
// What's the expected value of foo here?

На функциональном языке я знать что foo все еще 42. Мне даже не нужно Смотреть В коде между ними, гораздо меньше понимайте это или посмотрите на реализации функций, которые он вызывает.

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

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

Почему побочные эффекты считаются злыми?

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

На очень высоком уровне представьте, что тестируйте весь 3-уровневой веб-сайт только с тестированием черного ящика. Конечно, это выполнимо, в зависимости от шкалы. Но, безусловно, много дублирования происходит. И если там является Ошибка (которая связана с побочным эффектом), затем вы могли бы потенциально разбить всю систему для дальнейшего тестирования, пока ошибка не будет диагностирована и исправлена, а исправление будет развернуто в тестовой среде.

Преимущества

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

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

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

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

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

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

Ну, ИМХО, это довольно лицемерно. Никто не любит побочные эффекты, но все они нужны.

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

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

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

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

Любой побочный эффект вводит дополнительные параметры ввода/вывода, которые необходимо учитывать при тестировании.

Это делает проверку кода гораздо более сложной, поскольку среда не может быть ограничена только подтвержденным кодом, но должен привлечь некоторую или всю окружающую среду (глобальная, которая обновляется в этом коде, что, в свою очередь, зависит от этого Код, который, в свою очередь, зависит от жизни внутри полного сервера Java EE ....)

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

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

Например, возьмите базовое приложение для рабочего стола пользовательского интерфейса. У меня может быть работающая программа, которая имеет на своей куче график объекта, представляющий текущее состояние модели домена моей программы. Сообщения прибывают к объектам на этом графике (например, с помощью методов вызовов, вызванных контроллером уровня пользовательского интерфейса). График объекта (доменная модель) на куче изменяется в ответ на сообщения. Наблюдатели модели информируются о любых изменениях, пользовательском интерфейсе и, возможно, других ресурсах изменены.

Далеко не злой, правильное расположение этих побочных эффектов модифицирующего кучу и модификации экрана лежат в основе дизайна OO (в данном случае шаблон MVC).

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

Зло немного выше. Все зависит от контекста использования языка.

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

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

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

  • В коде с изменчивым состоянием мы можем управлять масштабами состояния таким образом, чтобы статически гарантировать, что оно не может протекать вне данной функции, что позволяет нам собирать мусор без счетов. , но все же будьте уверены, что ссылки не сохранились. Те же гарантии также полезны для поддержания чувствительной к конфиденциальности информации и т. Д. (Это может быть достигнуто с помощью Сент-Монады в Хаскелле)
  • При изменении общего состояния в нескольких потоках мы можем избежать необходимости в блокировках, отслеживая изменения и выполняя атомное обновление в конце транзакции, или откатываясь обратно транзакцией и повторяя ее, если другой поток внес противоречивую модификацию. Это достижимо только потому, что мы можем гарантировать, что код не имеет никаких эффектов, кроме как модификации состояния (которые мы можем с радостью отказаться). Это выполняется STM (программная транзакционная память) Monad в Haskell.
  • Мы можем отслеживать эффекты кода и тривиально песочного бокса, отфильтровав любые эффекты, которые он может выполнить, чтобы быть уверенным, что он безопасен, таким образом, позволяя (например,) Пользовательский код, который будет выполняться безопасно, на веб -сайте

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

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

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

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

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

Поэтому, когда у меня есть эти случаи, мне часто очень трудно, если не невозможно, чувствовать себя очень уверенно в правильности такого кода, не говоря уже о том, что я могу внести изменения в такой код, не преуспевая что -то неожиданное. Таким образом, решение для меня, есть либо упростить поток управления, либо минимизировать/объединить побочные эффекты (подключившись, я имею в виду, что приведет только к одному типу побочного эффекта во многих вещах во время конкретной фазы в системе, а не два, три или дюжина). Мне нужно, чтобы одна из этих двух вещей позволила моему Simpleton Brain почувствовать уверенность в правильности существующего кода и правильности изменений, которые я внедряю. Довольно легко быть уверенным в правильной коде, вводя побочные эффекты, если побочные эффекты являются однородными и простыми вместе с потоком управления, как и так:

for each pixel in an image:
    make it red

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

for each vertex to remove in a mesh:
     start removing vertex from connected edges():
         start removing connected edges from connected faces():
             rebuild connected faces excluding edges to remove():
                  if face has less than 3 edges:
                       remove face
             remove edge
         remove vertex

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

for each vertex to remove:
     mark connected edges
for each marked edge:
     mark connected faces
for each marked face:
     remove marked edges from face
     if num_edges < 3:
          remove face

for each marked edge:
     remove edge
for each vertex to remove:
     remove vertex

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
scroll top