Нарушает ли «foldp» принцип отсутствия изменяемого состояния FP?

Я узнаю об Elm из Еще семь языков за семь недель. Меня смущает следующий пример:

import Keyboard
main = lift asText (foldp (\dir presses -> presses + dir.x) 0 Keyboard.arrows)

foldp определяется как:

Signal.foldp : (a -> b -> b) -> b -> Signal a -> Signal b

Мне кажется, что:

  • начальное значение аккумулятора presses равно только 0 при первой оценке main
  • после первого вычисления main кажется, что начальное значение presses равно результату функции (a -> b -> b) или (\dir presses -> presses + dir.x) в примере, который был при предыдущем вычислении.

Если это действительно так, то не является ли это нарушением принципов функционального программирования, поскольку main теперь поддерживает внутреннее состояние (или, по крайней мере, foldp)?

Как это работает, когда я использую foldp в нескольких местах своего кода? Сохраняет ли он несколько внутренних состояний, по одному на каждый раз, когда я его использую?

Единственная другая альтернатива, которую я вижу, заключается в том, что foldp (в примере) начинает отсчет, так сказать, с 0 каждый раз, когда он оценивается, и каким-то образом сворачивает всю историю, предоставленную Keyboard.arrows. Мне кажется, что это чрезвычайно расточительно и обязательно вызовет исключения из-за нехватки памяти в течение длительного времени.

Я что-то упустил здесь?


person Marcel    schedule 26.07.2014    source источник


Ответы (3)


Как это работает

Да, foldp хранит какое-то внутреннее состояние. Сохранение всей истории было бы расточительным и не делается.

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

import Keyboard

plus  = (foldp (\dir presses -> presses + dir.x) 0 Keyboard.arrows)
minus = (foldp (\dir presses -> presses - dir.x) 0 Keyboard.arrows)
showThem p m = flow down (map asText [p, m])
main  = lift2 showThem plus minus

Но если вы дважды используете результирующий сигнал от foldp, в вашей скомпилированной программе будет только один экземпляр foldp, результирующие изменения просто будут использованы в двух местах:

import Keyboard

plus  = (foldp (\dir presses -> presses + dir.x) 0 Keyboard.arrows)
showThem p m = flow down (map asText [p, m])
main  = lift2 showThem plus plus

Главный вопрос

Если это действительно так, то не является ли это нарушением принципов функционального программирования, поскольку main теперь поддерживает внутреннее состояние (или, по крайней мере, foldp)?

У функционального программирования нет какого-то канонического определения, которым бы пользовались все. Есть много примеров функциональных языков программирования, которые позволяют использовать изменяемое состояние. Некоторые из этих языков программирования показывают вам, что значение может быть изменено в системе типов (вы можете видеть тип State a Haskell как таковой, хотя это действительно зависит от вашей точки зрения).

Но что такое изменчивое состояние? Что такое изменяемое значение? Это значение внутри программы, которое можно изменить. То есть может измениться. Это могут быть разные вещи в разное время. Ах, но мы же знаем, как Elm называет значения изменяющимися с течением времени! Это Signal.
Так что на самом деле Signal в Elm — это значение, которое может меняться со временем, и поэтому его можно рассматривать как переменную, изменяемое значение или изменяемое состояние. Просто мы очень строго управляем этим значением, разрешая только несколько хорошо выбранных манипуляций с Signals. Такие Signal могут быть основаны на других Signal в вашей программе, или поступать из библиотеки, или поступать из внешнего мира (подумайте о таких входных данных, как Mouse.position). И кто знает, как внешний мир дошел до этого сигнала! Таким образом, разрешить вашим собственным Signals основываться на прошлом значении Signals на самом деле нормально.

Заключение / TL;DR

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

person Apanatshka    schedule 26.07.2014
comment
Ваша точка зрения насчет канонических определений хорошо понята. Мне кажется, что между оценками одного и того же утверждения происходит какое-то волшебство. Вы говорите, что в Elm Signals являются обертками для изменяемого состояния, очень похожими на монаду Haskell State, и, конечно же, foldp возвращает Signal b. Но - из определения Signal.foldp : (a -> b -> b) -> b -> Signal a -> Signal b - аккумулятор - это просто b, а не Signal b. Между оценками Signal b волшебным образом разворачивается и вставляется как b, игнорируя 0 при последующих оценках. Я думаю, нотация меня просто беспокоит :-) - person Marcel; 27.07.2014
comment
Не могли бы вы сказать, что такое же волшебное развертывание происходит для a, а также происходит и для Signal.lift : (a -> b) -> Signal a -> Signal b? В любом случае, я понимаю, что это может показаться немного странным. Вы уже видели график сигналов? Они объясняются в разделе 4 seas.harvard.edu. /sites/default/files/files/archived/. Там вы видите, что узел foldp имеет внутреннее состояние. Но вы также можете увидеть это по-другому, как замкнутую петлю, в которой foldp использует свой выходной сигнал в качестве входного: groups.google.com/group/elm-discuss/attach/4b33ca584290b011/ - person Apanatshka; 27.07.2014
comment
Я посмотрю и пока просто буду пахать, я уверен, что преодолею свое первоначальное ощущение, что утверждение «забавно пахнет», когда я больше привыкну к семантике Elm. Спасибо за дополнительные ссылки, я воспользуюсь ими, чтобы прочитать foldp. - person Marcel; 28.07.2014
comment
См. также мой комментарий к CheatEx, чтобы дать вам представление о том, почему мне так смешно пахнет ;-) - person Marcel; 28.07.2014
comment
Как это называется foldp а не просто фолд? - person CMCDragonkai; 02.10.2014
comment
Складывание @CMCDragonkai — это общая концепция. Вы можете свернуть список слева или справа, названный foldl и foldr соответственно. Таким образом, сигнал фолда также получает направление: вы фолдите из прошлого. - person Apanatshka; 02.10.2014
comment
Да, я понял, когда читал статью Эвана. Хм, не было бы чище, чтобы все они назывались fold и использовали специальный полиморфизм. - person CMCDragonkai; 02.10.2014
comment
@CMCDragonkai Elm в настоящее время не имеет функциональности, подобной классу типов, поэтому вам придется передавать словари с функцией fold в нем. Пока проще просто добавить букву. - person Apanatshka; 06.10.2014

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

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

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

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

person Karl Bielefeldt    schedule 12.09.2014

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

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

person CheatEx    schedule 27.07.2014
comment
Теперь, когда я больше об этом думаю, меня беспокоит семантика оператора, так как определение, которое вы даете — все функции возвращают один и тот же результат при одинаковых входных данных — не похоже на< /i> держать. При втором вычислении foldp предоставленный аккумулятор — b или presses или 0 — бесцеремонно сбрасывается в пользу некоторого внутреннего состояния. Наверняка вы согласитесь, что на самом деле это не похоже на возврат одного и того же вывода при одинаковом вводе :-) - person Marcel; 28.07.2014
comment
@MarcelOomens Извините, я не совсем понимаю, что вас смущает. В частности, что вы подразумеваете под второй оценкой foldp? В вашем примере он оценивается только один раз. - person CheatEx; 29.07.2014
comment
@MarcelOomens foldp ДЕЙСТВИТЕЛЬНО возвращает один и тот же вывод, когда ему дается один и тот же ввод - он возвращает бесконечный сигнал. Форма этого сигнала определяется функцией аккумулятора и начальным значением. - person fbonetti; 02.09.2015