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]