Использование периодических и сетевых соединений по сети

Я пытаюсь написать основу для интерактивной графики в реальном времени на Haskell. Я пытался справиться с вещами, используя Netwire 5, но мне кажется, что я не очень хорошо понимаю, как все "зависит" друг от друга. Например, следующий код должен выдать val за две секунды до переключения на (val + 1), а затем продолжать бесконечно долго.

someWire :: Num a => a -> Wire s e m a a
someWire x = (-->) ((pure x) &&& (periodic 2) >>> until) (someWire (x + 1))

Однако это приводит к некоторой утечке памяти, когда моя программа останавливается и просто продолжает выделять память, пока что-то в моей системе не падает. В качестве альтернативы это определение

someWire :: Num a => a -> Wire s e m a a
someWire x = (-->) ((pure x) &&& (at 2) >>> until) (someWire (x + 1))

ведет себя так, как я ожидаю: считать от val далее значение меняется каждые две секунды. Может кто-нибудь объяснить, пожалуйста, это поведение?

2 ответа

Решение

Ключевое понимание заключается в том, что periodic производит событие немедленно.

Следовательно, когда мы собираемся произвести значение из этого провода, мы должны оценить его следующим образом:

someWire x
(-->) ((pure x) &&& (periodic 2) >>> until) (someWire (x + 1))
(-->) (pure (x, Event _) >>> until) (someWire (x + 1))
(-->) *inhibition* (someWire (x + 1))
someWire (x + 1)

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

Давайте посмотрим на рабочую версию:

import Control.Wire hiding (until)
import qualified Control.Wire (until) as W

someWire :: Num a => a -> Wire s e m a a
someWire x = (-->) (pure x &&& at 2 >>> W.until) (someWire (x + 1))

-- To test in GHCi: testWire clockSession_ $ someWire 3 

Wireс Arrows, и поэтому будет легче понять, что происходит, если мы сможем следить за тем, что делают комбинаторы стрелок.

(&&&) :: Arrow a => a b c -> a b c' -> a b (c, c')

(&&&) строит стрелку, которая разветвляет свой ввод (который в нашем случае можно рассматривать как сердцебиение, управляющее событиями) в пару, применяя одну стрелку к каждому компоненту. Здесь мы разветвляемся со стрелкой, которая всегда производит x и стрелка, которая возвращает событие, которое должно произойти через две секунды.

(>>>) :: Category cat => cat a b -> cat b c -> cat a c

(>>>) это просто композиция стрелок (Arrow это подкласс Category), написано в первом-втором-порядке. Итак, мы отправляем вывод pure x &&& at 2 в W.until,

W.until :: Monoid e => Wire s e m (a, Event b) a

Составление с W.until преобразует стрелку, которая создает значение в паре с событием (например, pure x &&& at 2) в стрелку, которая производит значение, пока событие не произойдет. Как только это произойдет, мы переключимся на другой провод, используя (-->),

Теперь должно быть проще понять, почему вы не можете использовать periodic вместо at, at происходит в одно мгновение, в то время как periodic продолжает происходить, и так W.until никогда не заканчивается (Если вы покопаетесь в источниках, вы обнаружите, что W.until делает что-то похожее на события, что соответствует интуитивному объяснению.)

Другие вопросы по тегам