Использование периодических и сетевых соединений по сети
Я пытаюсь написать основу для интерактивной графики в реальном времени на 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
с Arrow
s, и поэтому будет легче понять, что происходит, если мы сможем следить за тем, что делают комбинаторы стрелок.
(&&&) :: 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
делает что-то похожее на события, что соответствует интуитивному объяснению.)