Сохранение государства на чисто функциональном языке - PullRequest
10 голосов
/ 20 мая 2011

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


(def set-point (ref {:sp 90}))

(while true
  (let [curr (read-speed)]
    (controller @set-point curr)))


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

Ответы [ 5 ]

15 голосов
/ 20 мая 2011

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

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

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

Я выбрал atom, но в зависимости от ваших требований может быть более целесообразно использовать ref или agent ,

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

(def motor (atom {:speed 0} :validator (comp not neg? :speed)))

(defn add-speed [n]
  (swap! motor update-in [:speed] + n))

(defn set-speed [n]
  (swap! motor update-in [:speed] (constantly n)))

> (add-speed 10)
> (add-speed -8)
> (add-speed -4) ;; This will not change the state of motor
                 ;; since the speed would drop below zero and
                 ;; the validator does not allow that!
> (:speed @motor)
2
> (set-speed 12)
> (:speed @motor)
12

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

  • Если вы хотите асинхронно изменять скорость двигателя, вы должны использовать агента. Затем вам нужно изменить swap! на send. Это было бы полезно, если, например, клиенты, регулирующие скорость двигателя, отличаются от клиентов, использующих скорость двигателя, так что вполне возможно, что скорость будет изменена «в конце концов».

  • Другим вариантом является использование ref, которое подойдет, если двигатель должен координироваться с другими идентификаторами в вашей системе. Если вы выбираете этот тип ссылки, вы меняете swap! на alter. Кроме того, все изменения состояния выполняются в транзакции с dosync, чтобы гарантировать, что все идентификационные данные в транзакции обновляются атомарно.

Монады не нужны для моделирования личности и состояния в Clojure!

8 голосов
/ 20 мая 2011

Для этого ответа я собираюсь интерпретировать «чисто функциональный язык» как означающий «язык в стиле ML, исключающий побочные эффекты», который я, в свою очередь, буду интерпретировать как означающий «Haskell», который я буду интерпретировать как значение"GHC".Ничто из этого не является строго правдивым, но, учитывая, что вы сравниваете это с производным от Лиспа и что GHC довольно заметен, я предполагаю, что это все равно станет основой вашего вопроса.

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

Примитивы изменчивости обычно предоставляются языковой средой выполнения, идоступ через функции, которые производят значения в некоторой монаде, также предоставляемой средой выполнения (часто IO, иногда более специализированными).Во-первых, давайте посмотрим на предоставленный вами пример Clojure: он использует ref, который описан в документации здесь :

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

Забавно, что весь этот абзац довольно прямо переводится в GHC Haskell.Я предполагаю, что «Vars» эквивалентны Haskell MVar, в то время как «Refs» почти наверняка эквивалентны TVar, как указано в пакете stm .

Итак, чтобы перевести пример на Haskell, нам понадобится функция, которая создает TVar:

setPoint :: STM (TVar Int)
setPoint = newTVar 90

... и мы можем использовать ее в коде, подобном этому:

updateLoop :: IO ()
updateLoop = do tvSetPoint <- atomically setPoint
                sequence_ . repeat $ update tvSetPoint
  where update tv = do curSpeed <- readSpeed
                       curSet   <- atomically $ readTVar tv
                       controller curSet curSpeed

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

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

3 голосов
/ 20 мая 2011

Методом решения проблем, которые, по-видимому, кричат ​​ для изменчивости (например, GUI или веб-приложения) функциональным способом, является Функциональное реактивное программирование .

2 голосов
/ 20 мая 2011

В Erlang вы можете использовать процесс для хранения значения.Как то так:

holdVar(SomeVar) ->
  receive %% wait for message
    {From, get} ->             %% if you receive a get
      From ! {value, SomeVar}, %% respond with SomeVar
      holdVar(SomeVar);        %% recursively call holdVar
                               %% to start listening again

    {From, {set, SomeNewVar}} -> %% if you receive a set
      From ! {ok},               %% respond with ok
      holdVar(SomeNewVar);       %% recursively call holdVar with
                                 %% the SomeNewVar that you received 
                                 %% in the message
  end.
2 голосов
/ 20 мая 2011

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

В качестве краткого неформального объяснения монад:

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...