Haskell Netwire: провода из проводов

Я играю с пакетом netwire, пытаясь почувствовать FRP, и у меня есть быстрый вопрос.

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

myWire :: (Monad m, HasTime t s) => Wire s () m a Float
myWire = timeF

myWire' :: (Monad m, HasTime t s) => Wire s () m a Int
myWire' = fmap round myWire

myEvent :: (Monad m, HasTime t s) => Wire s () m a (Event Int)
myEvent = periodic 5 . myWire'

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

eventList :: (Monad m, HasTime t s) 
            => Wire s () m a (Event [Wire s () m a Int])
eventList = accumE go [] . myEvent
  where go soFar x = f x : soFar
        f x = for 10 . pure x --> pure 0

Затем я представляю новый провод, который будет тормозить до eventList начинает вызывать события, вот так:

myList :: (Monad m, HasTime t s) => Wire s () m a [Wire s () m a Int]
myList = asSoonAs . eventList

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

myNums :: (Monad m, HasTime t s) => Wire s () m [Wire s () m a Int] [Int]
myNums = mkGen $ \dt wires -> do
  stepped <- mapM (\w -> stepWire w dt $ Right undefined) wires
  let alive = [ (r, w) | (Right r, w) <- stepped ]
  return (Right (map fst alive), myNums)

myNumList :: (Monad m, HasTime t s) => Wire s () m a [Int]
myNumList = myNums . myList

И, наконец, у меня есть моя основная процедура, чтобы проверить все это:

main = testWire clockSession_ myNumList

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

[0]
[5, 0]
[10, 5, 0]
[15, 10, 0, 0]

и так далее. Что я на самом деле вижу

[0]
[5, 0]
[10, 5, 0]
[15, 10, 5, 0]

Итак, я знаю, что моя функция аккумулятора работает: каждое созданное событие преобразуется в провод. Но я не вижу, чтобы эти провода излучали разные значения с течением времени. Мое заявление for 10 . pure x --> pure 0 следует переключить их на излучение 0 по истечении времени.

Я все еще новичок в FRP, поэтому, возможно, я в корне неправильно понимаю что-то важное в этом (вероятно, так).

2 ответа

Решение

Проблема в том, что провода, генерируемые из событий, не являются постоянными. Заданное значение типа Wire s e m a b на самом деле экземпляр во времени функции, которая производит значение типа b от значения типа a, Поскольку Haskell использует неизменяемые значения, для пошагового подключения нужно что-то сделать с полученным проводом из stepWire в противном случае вы получите тот же вывод для того же ввода. Посмотрите на результаты myList:

Event 1: [for 10 . pure 0 --> pure 0]
Event 2: [for 10 . pure 5 --> pure 0, for 10 . pure 0 --> pure 0]
Event 3: [for 10 . pure 10 --> pure 0, for 10 . pure 5 --> pure 0, for 10 . pure 0 --> pure 0]
... etc

Когда вы переходите каждый из этих проводов, вы получите только [.., 10, 5, 0] каждый раз, потому что вы повторно используете исходное значение for 10 . pure x --> pure 0 провод. Посмотрите на подпись stepWire:

stepWire :: Monad m => Wire s e m a b -> s -> Either e a -> m (Either e b, Wire s e m a b)

Это означает, что для такого заявления, как

(result, w') <- stepWire w dt (Right underfined)

... w' следует использовать в следующий раз, когда вам нужно позвонить stepWireпотому что это поведение в следующий момент времени. Если у вас есть провод, который производит провода, то вам нужно где-то разгрузить полученные провода, чтобы их можно было обрабатывать отдельно.

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

$ ghc -o test test.hs
[1 of 1] Compiling Main             ( test.hs, test.o )
Linking test ...
$ ./test
[0]
[5,0]
[10,5,0]
[15,10,0,0]
[20,15,0,0,0]
...

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

      [] -- we already know about
w0' : [] -- we got as input
w0' : [] -- what we should keep

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

            w2  : w1  : w0  : [] -- we already know about
w4' : w3' : w2' : w1' : w0' : [] -- we got as input
w4' : w3' : w2  : w1  : w0  : [] -- what we should keep

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

makePrefixOf :: [a] -> [a] -> [a]
makePrefixOf [] ys = ys
makePrefixOf xs [] = xs
makePrefixOf (x:xs) (_:ys) = x:makePrefixOf xs ys

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

makeSuffixOf :: [a] -> [a] -> [a]
makeSuffixOf xs ys = reverse $ makePrefixOf (reverse xs) (reverse ys)

Теперь мы можем реализовать myNums отслеживание которых oldWires у нас уже есть государство для.

myNums :: (Monad m, HasTime t s) => Wire s () m [Wire s () m a b] [b]
myNums = go []
  where
    go oldWires = mkGen $ \dt newWires -> do
        let wires = makeSuffixOf oldWires newWires
        stepped <- mapM (\w -> stepWire w dt $ Right undefined) wires
        let alive = [ (r, w) | (Right r, w) <- stepped ]
        return (Right (map fst alive), go (map snd alive))

Если мы хотим быть педантичными, мы должны использовать список Maybe провода, так что когда провода больше нет в живых, мы можем оставить позади Nothing на его месте, так что списки проводов все еще совпадают. Если мы сделаем это, даже с умным представлением для состояния, мы потеряем пространство, когда провода умирают. Их первоначальное определение будет по-прежнему бродить в списке, накопленном eventList,

Это дает желаемый результат

            [ 0]
         [ 5, 0]
      [10, 5, 0]
   [15,10, 0, 0]
[20,15, 0, 0, 0]
Другие вопросы по тегам