Я вижу преимущества изменчивых по сравнению с неизменяемыми объектами, такими как неизменяемые объекты, которые устраняют много трудностей при устранении неполадок в многопоточном программировании из-за общего и записываемого состояния. Напротив, изменяемые объекты помогают иметь дело с идентичностью объекта, а не каждый раз создавать новую копию и, таким образом, также улучшают производительность и использование памяти, особенно для более крупных объектов.
Одна вещь, которую я пытаюсь понять, - это то, что может пойти не так, если у вас есть изменяемые объекты в контексте функционального программирования. Как один из моментов, которые мне сказали, заключается в том, что результат вызова функций в другом порядке не является детерминированным.
Я ищу реальный конкретный пример, где очень очевидно, что может пойти не так, используя изменяемый объект в программировании функций. В принципе, если это плохо, это плохо независимо от ОО или парадигмы функционального программирования, верно?
Я считаю, что ниже мое собственное утверждение само отвечает на этот вопрос. Но все же мне нужен пример, чтобы я мог чувствовать это более естественно.
ОО помогает управлять зависимостями и писать более простые и поддерживаемые программы с помощью таких инструментов, как инкапсуляция, полиморфизм и т. Д.
Функциональное программирование также имеет тот же мотив продвижения поддерживаемого кода, но с использованием стиля, который устраняет необходимость использования ОО-инструментов и методов - один из которых, я считаю, сводится к минимуму побочных эффектов, чистой функции и т. Д.
Ответы:
Я думаю, что важность лучше всего продемонстрировать, сравнивая с ОО-подходом
например, скажем, у нас есть объект
В парадигме ОО метод привязан к данным, и имеет смысл, чтобы эти данные были мутированы методом.
В функциональной парадигме мы определяем результат в терминах функции. приобретенный порядок IS результата функции покупки , примененная к порядку. Это подразумевает несколько вещей, в которых мы должны быть уверены
Вы ожидаете order.Status == "Куплено"?
Это также означает, что наши функции являются идемпотентными. то есть. запуск их дважды должен давать один и тот же результат каждый раз.
Если заказ был изменен с помощью функции покупки, купленный заказ2 потерпит неудачу.
Определяя вещи как результаты функций, это позволяет нам использовать эти результаты, фактически не вычисляя их. Что в терминах программирования является отложенным исполнением.
Это само по себе может быть удобно, но как только мы не уверены в том, когда функция на самом деле произойдет, и у нас все хорошо, мы можем использовать параллельную обработку гораздо больше, чем мы можем в парадигме ОО.
Мы знаем, что запуск функции не повлияет на результаты другой функции; таким образом, мы можем оставить компьютер для выполнения их в любом порядке, который он выберет, используя столько потоков, сколько он пожелает.
Если функция изменяет свой ввод, мы должны быть намного осторожнее с такими вещами.
источник
Order Purchase() { return new Order(Status = "Purchased") }
так, что статус будет доступен только для чтения. ? Опять же, почему эта практика более актуальна в контексте парадигмы программирования функций? Упомянутые вами преимущества можно увидеть и в ОО-программировании, верно?Ключ к пониманию того, почему неизменяемые объекты полезны, на самом деле заключается не в попытках найти конкретные примеры в функциональном коде. Поскольку большая часть функционального кода написана с использованием функциональных языков, а большинство функциональных языков являются неизменяемыми по умолчанию, сама природа парадигмы разработана таким образом, чтобы избежать того, что вы ищете.
Ключевой вопрос, который нужно спросить, что это за преимущество неизменности? Ответ в том, что он избегает сложности. Скажем, у нас есть две переменные,
x
иy
. Оба начинаются со значения1
.y
хотя удваивается каждые 13 секунд. Какова будет ценность каждого из них через 20 дней?x
будет1
. Это легко. Хотя это потребует усилий, такy
как это намного сложнее. Какое время суток через 20 дней? Нужно ли учитывать летнее время? Сложностьy
противx
гораздо больше.И это происходит в реальном коде тоже. Каждый раз, когда вы добавляете изменяющееся значение к миксу, это становится еще одним сложным значением, которое вы можете удерживать и вычислять в своей голове или на бумаге, когда пытаетесь написать, прочитать или отладить код. Чем сложнее, тем выше вероятность того, что вы допустите ошибку и внесете ошибку. Код сложно написать; трудно читать; трудно отладить: код трудно понять правильно.
Изменчивость не плохо, хотя. Программа с нулевой изменчивостью не может иметь никакого результата, что довольно бесполезно. Даже если изменчивость заключается в записи результата на экран, диск или что-то еще, он должен быть там. Что плохо, так это ненужная сложность. Один из самых простых способов уменьшить сложность состоит в том, чтобы сделать объекты неизменяемыми по умолчанию и сделать их изменяемыми только при необходимости из-за производительности или функциональных причин.
источник
y
должен мутировать; это требование. Иногда мы должны иметь сложный код для удовлетворения сложных требований. Суть, которую я пытался подчеркнуть, заключается в том, что ненужных сложностей следует избегать. Мутирующие значения по своей природе более сложны, чем фиксированные, поэтому, чтобы избежать ненужной сложности, изменяйте значения только тогда, когда это необходимо.Те же самые вещи, которые могут пойти не так в нефункциональном программировании: вы можете получить нежелательные, неожиданные побочные эффекты , которые являются хорошо известной причиной ошибок со времени изобретения языков программирования с определенной областью действия.
ИМХО, единственная реальная разница в этом между функциональным и нефункциональным программированием заключается в том, что в нефункциональном коде вы, как правило, ожидаете побочных эффектов, в функциональном программировании вы не будете.
Конечно, нежелательные побочные эффекты являются категорией ошибок, независимо от парадигмы. Также верно и обратное: намеренно используемые побочные эффекты могут помочь решить проблемы с производительностью и, как правило, необходимы для большинства реальных программ, когда речь идет о вводе-выводе и работе с внешними системами - также независимо от парадигмы.
источник
Я только что ответил на вопрос StackOverflow, который достаточно хорошо иллюстрирует ваш вопрос. Основная проблема с изменчивыми структурами данных заключается в том, что их идентичность действительна только в один точный момент времени, поэтому люди стремятся втиснуть как можно больше в небольшую точку кода, где они знают, что идентичность постоянна. В этом конкретном примере он делает много записей внутри цикла for:
Когда вы привыкли к неизменности, не будет опасений, что структура данных изменится, если вы будете ждать слишком долго, поэтому вы можете выполнять задачи, которые логически разделены на досуге, в гораздо большей степени разъединенными способами:
источник
Преимущество использования неизменяемых объектов состоит в том, что если кто-то получает ссылку на объект, у которого будет определенное свойство, когда получатель проверяет его, и ему нужно дать некоторому другому коду ссылку на объект с тем же свойством, то можно просто передать вдоль ссылки на объект без учета того, кто еще мог получить ссылку или что они могли бы сделать с объектом [поскольку никто ничего не может сделать с объектом], или когда получатель мог бы исследовать объект [так как все его свойства будут одинаковыми независимо от того, когда они проверяются].
Напротив, код, который должен дать кому-то ссылку на изменяемый объект, который будет иметь определенное свойство, когда получатель проверяет его (при условии, что сам получатель не изменяет его), либо должен знать, что ничто, кроме получателя, никогда не изменится это свойство, или же знать, когда получатель получит доступ к этому свойству, и знать, что ничто не изменит это свойство, пока получатель не будет в последний раз проверять его.
Я думаю, что для программирования в целом (а не только для функционального программирования) наиболее полезно думать о неизменных объектах как о трех категориях:
Объекты, которые не могут ничего изменить, даже со ссылкой. Такие объекты и ссылки на них ведут себя как значения и могут свободно использоваться.
Объекты, которые могли бы быть изменены самим кодом, который имеет ссылки на них, но чьи ссылки никогда не будут выставлены никакому коду, который фактически изменит их. Эти объекты инкапсулируют значения, но ими можно поделиться только с кодом, которому можно доверять, не изменяя их или не подвергая их воздействию кода, который может это сделать.
Объекты, которые будут изменены. Эти объекты лучше всего рассматривать как контейнеры , а ссылки на них - как идентификаторы .
Полезный шаблон часто заключается в том, что объект создает контейнер, заполняет его с помощью кода, которому можно доверять, чтобы он не сохранял ссылку впоследствии, а затем имел бы единственные ссылки, которые когда-либо будут существовать где-либо в юниверсе, в коде, который никогда не изменит объект, как только он заполнен. Хотя контейнер может быть изменяемого типа, он может рассуждать о (*) как о неизменяемости, поскольку на самом деле его никогда не будет мутировать. Если все ссылки на контейнер хранятся в неизменяемых типах оболочек, которые никогда не изменят его содержимое, такие оболочки могут безопасно передаваться, как если бы данные в них содержались в неизменяемых объектах, поскольку ссылки на оболочки могут свободно передаваться и проверяться в любое время.
(*) В многопоточном коде может потребоваться использовать «барьеры памяти», чтобы гарантировать, что прежде чем какой-либо поток сможет увидеть какую-либо ссылку на оболочку, эффекты всех действий над контейнером будут видны этому потоку, но это особый случай, упомянутый здесь только для полноты.
источник
Как уже упоминалось, проблема с изменяемым состоянием в основном является подклассом более крупной проблемы побочных эффектов , когда возвращаемый тип функции не точно описывает, что функция действительно делает, потому что в этом случае она также выполняет мутацию состояния. Эта проблема была решена в некоторых новых исследовательских языках, таких как F * ( http://www.fstar-lang.org/tutorial/ ). Этот язык создает систему эффектов, аналогичную системе типов, где функция не только статически объявляет свой тип, но и свои эффекты. Таким образом, вызывающие функции осознают, что при вызове функции может происходить мутация состояния, и этот эффект распространяется на ее вызывающих.
источник