Подтвердить что ты не робот

Какая связь между Iteratees и FRP?

Мне кажется, что существует сильная связь между этими двумя идеями. Я предполагаю, что FRP может быть реализован в терминах Iteratees, если бы был способ выразить произвольные графики с помощью Iteratees. Но афаик поддерживает только цепные структуры.

Может ли кто-то пролить свет на это?

4b9b3361

Ответ 1

Это наоборот. Существует сильная связь между AFRP и потоковой обработкой. Фактически AFRP - это форма обработки потока, и вы можете использовать идиому для реализации чего-то очень похожего на трубы:

data Pipe m a b =
    Pipe {
      cleanup :: m (),
      feed    :: [a] -> m (Maybe [b], Pipe m a b)
    }

Это расширение категорий проводников, найденных в Netwire. Он получает следующий фрагмент ввода и возвращает Nothing, когда он перестает производить. Используя это, программа чтения файлов будет иметь следующий тип:

readFile :: (MonadIO m) => FilePath -> Pipe m a ByteString

Труба - это семейство аппликативных функторов, поэтому для применения простой функции к элементам потока вы можете просто использовать fmap:

fmap (B.map toUpper) . readFile

Для вашего удобства это также семья профуклоров.

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

Ответ 2

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

mouseCoordinates :: (Proxy p) => () -> Producer p MouseCoord IO r

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

coordHandler :: (Proxy p) => () -> Consumer p MouseCoord IO r

Затем вы подключите события мыши к обработчику с помощью композиции:

>>> runProxy $ mouseCoordinates >-> coordHandler

И он будет работать так, как вы ожидаете.

Как вы сказали, это хорошо работает для одной цепочки этапов, но как насчет более произвольных топологий? Ну, оказывается, так как центральный Proxy тип pipes является монадным трансформатором, вы можете смоделировать любую произвольную топологию, просто вложив в нее прокси-модемные трансформаторы. Например, вот как вы могли бы закрепить два входных потока:

zipD
 :: (Monad m, Proxy p1, Proxy p2, Proxy p3)
 => () -> Consumer p1 a (Consumer p2 b (Producer p3 (a, b) m)) r
zipD () = runIdentityP $ hoist (runIdentityP . hoist runIdentityP) $ forever $ do
    a <- request ()               -- Request from the outer Consumer
    b <- lift $ request ()        -- Request from the inner consumer
    lift $ lift $ respond (a, b)  -- Respond to the Producer

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

-- 1st application
p1 = runProxyK $ zipD   <-< fromListS [1..]

-- 2nd application
p2 = runProxyK $ p2     <-< fromListS [4..6]

-- 3rd application
p3 = runProxy  $ printD <-< p3

Он работает так, как вы ожидаете:

>>> p3
(1, 4)
(2, 5)
(3, 6)

Этот трюк обобщается на любую топологию. Подробнее об этом можно узнать в Control.Proxy.Tutorial в разделе "Ветки, молнии и слияния". В частности, вы должны проверить комбинатор fork, который он использует в качестве примера, который позволяет разделить поток на два выхода.