На чисто функциональном языке нельзя было еще определить оператор «присваивания», скажем «‹ - », так, чтобы команда, скажем,« i ‹- 3», вместо прямого присвоения неизменяемой переменной i, создавала копию всего текущего стека вызовов, за исключением замены i на 3 в новом стеке вызовов и выполнения нового стека вызовов с этого момента? Учитывая, что на самом деле никакие данные не изменились, разве это не будет считаться «чисто функциональным» по определению? Конечно, компилятор просто сделает оптимизацию, чтобы просто присвоить 3 переменной i, и в этом случае какая разница между императивным и чисто функциональным?
Действительно ли чисто функциональные языки гарантируют неизменность?
Ответы (3)
Haskell не дает вам способов самоанализа или «выполнения» стеков вызовов, так что я бы не стал особо беспокоиться об этой странной схеме. Однако в целом верно, что можно разрушить систему типов, используя небезопасные "функции", такие как unsafePerformIO :: IO a -> a
. Идея состоит в том, чтобы сделать нарушение чистоты трудным, а не невозможным.
Действительно, во многих ситуациях, например, при создании привязок Haskell для библиотеки C, эти механизмы совершенно необходимы ... используя их, вы снимаете бремя доказательства чистоты с компилятора и берете его на себя.
Есть предложение фактически гарантировать безопасность, объявив вне закона такие подрывные версии системы типов; Я не слишком знаком с этим, но вы можете прочитать об этом здесь.
getCallStack :: Stack
был хоть сколько-нибудь полезен, он должен был бы давать разные Stack
в зависимости от того, где вы были в своей программе, что, очевидно, нарушает ссылочную прозрачность.
- person Tom Crockett; 10.04.2011
У чисто функциональных языков, таких как Haskell, есть способы моделирования императивных языков, и они не стесняются это признавать. :)
См., В частности, http://www.haskell.org/tutorial/io.html 7.5:
Итак, в конце концов, неужели Haskell просто заново изобрел императивное колесо?
В каком-то смысле да. Монада ввода-вывода составляет небольшой императивный подъязык внутри Haskell, и поэтому компонент ввода-вывода программы может казаться похожим на обычный императивный код. Но есть одно важное различие: нет специальной семантики, с которой нужно иметь дело. В частности, эквациональные рассуждения в Haskell не нарушены. Императивное ощущение монадического кода в программе не умаляет функционального аспекта Haskell. Опытный функциональный программист должен суметь минимизировать императивный компонент программы, используя только монаду ввода-вывода для минимального количества операций верхнего уровня. Монада четко разделяет функциональные и императивные программные компоненты. Напротив, императивные языки с функциональными подмножествами обычно не имеют четко определенного барьера между чисто функциональным и императивным мирами.
Таким образом, ценность функциональных языков не в том, что они делают невозможным изменение состояния, а в том, что они предоставляют способ, позволяющий хранить чисто функциональные части вашей программы отдельно от частей, изменяющих состояние.
Конечно, вы можете проигнорировать это и написать всю свою программу в императивном стиле, но тогда вы не воспользуетесь возможностями языка, так зачем его использовать?
Обновить
Ваша идея не так ошибочна, как вы думаете. Во-первых, если кто-то, знакомый только с императивными языками, захочет перебрать диапазон целых чисел, он может задаться вопросом, как этого можно достичь без увеличения счетчика.
Но, конечно, вместо этого вы просто пишете функцию, которая действует как тело цикла, а затем заставляете ее вызывать себя. Каждый вызов функции соответствует «шагу итерации». И в рамках каждого вызова параметр имеет другое значение, действуя как увеличивающаяся переменная. Наконец, среда выполнения может заметить, что рекурсивный вызов появляется в конце вызова, и поэтому она может повторно использовать верхнюю часть стека вызовов функций вместо того, чтобы увеличивать ее (хвостовой вызов). Даже в этом простом шаблоне есть почти весь вкус вашей идеи - включая компилятор / среду выполнения, незаметно вмешивающуюся и фактически вызывающую мутацию (перезапись вершины стека). Это не только логически эквивалентно циклу с изменяющимся счетчиком, но и фактически заставляет процессор и память делать то же самое физически.
Вы упоминаете GetStack
, который вернет текущий стек как структуру данных. Это действительно было бы нарушением функциональной чистоты, учитывая, что он обязательно будет возвращать что-то другое при каждом вызове (без аргументов). Но как насчет функции CallWithStack
, которой вы передаете свою собственную функцию, а она обращается к вашей функции и передает ей текущий стек в качестве параметра? Это было бы нормально. Примерно так работает CallCC.
MonadCont m => ((a -> m b) -> m a) -> m a
. Монада продолжения улавливает семантику продолжений и, таким образом, позволяет вам работать таким образом (пока вы остаетесь внутри нее).
- person Antal Spector-Zabusky; 10.04.2011
Неизменяемость - это свойство языка, а не реализации.
Операция a <- expr
, которая копирует данные, по-прежнему является обязательной операцией, если значения, относящиеся к местоположению a
, кажутся измененными с точки зрения программистов.
Точно так же реализация чисто функционального языка может перезаписывать и повторно использовать переменные по своему усмотрению, пока каждая модификация невидима для программиста. Например, функция map
может в принципе перезаписать список вместо создания нового, когда реализация языка может сделать вывод, что старый список нигде не понадобится.