Как работает функция mapAccum в Reactive Banana?
Здесь я рассмотрел ряд ответов на вопросы о переполнении стека, пытаясь найти решение моей проблемы с использованием библиотеки Reactive Banana. Все ответы используют магию с использованием "mapAccum", что я не совсем понимаю. Глядя на документацию по API, все, что я нахожу, это "Эффективная комбинация accumE
а также accumB
", что не очень полезно.
Похоже, что эту функцию можно использовать для сравнения значений Behavior
во время двух последовательных событий, что я хотел бы сделать. Но мне не ясно, как заставить это работать.
Как именно mapAccum
Работа?
3 ответа
Заметить, что
mapAccum :: acc -> Event t (acc -> (x, acc)) -> (Event t x, Behavior t acc)
поэтому он принимает начальное значение :: acc
накапливать, и Событие, которое производит функцию, которая обновляет это накопленное значение, в то же время производя выходное значение ::x
, (Обычно вы делаете такое событие, частично применяя некоторую функцию через <$>
.) В результате вы получаете новое событие, которое запускает ваш x
значения, когда они появляются, и поведение, содержащее ваше текущее накопленное значение.
использование mapAccum
если у вас есть событие, и вы хотите сделать связанное поведение и событие.
Например, в вашей проблемной области из вашего другого вопроса, предположим, у вас есть событие eTime :: Event t Int
который сработал хаотично, и вы хотели рассчитать eDeltaTime :: Event t Int
для различий и bTimeAgain :: Behaviour t Int
на текущее время:
type Time = Int
type DeltaTime = Time
getDelta :: Time -> Time -> (DeltaTime,Time)
getDelta new old = (new-old,new)
Я мог бы написать это getDelta new = \old -> (new-old,new)
чтобы сделать следующий шаг более понятным:
deltaMaker :: Event t (Time -> (DeltaTime,Time))
deltaMaker = getDelta <$> eTime
(eDeltaT,bTimeAgain) = mapAccum 0 $ deltaMaker
В этом случае, bTimeAgain
было бы поведение с тем же значением, что и события в eTime
, Это происходит потому, что мой getDelta
функция проходит new
прямо через без изменений от eTime
к acc
значение. (Если бы я хотел bTimeAgain
сам по себе, я бы использовал stepper :: a -> Event t a -> Behaviour t a
.) Если мне не нужно bTimeAgain
Я мог бы просто написать (eDeltaT,_) = mapAccum 0 $ deltaMaker
,
mapAccum
функция очень похожа на mapAccumL
функция из стандартного модуля Data.List, отсюда и название. Документация по Hackage также содержит ссылку на исходный код mapAccum
, Надеемся, что вместе с сигнатурой типа этой информации будет достаточно, чтобы понять, как работает эта функция.
Опять же, я мог бы просто улучшить документацию.:-) Но я не совсем понимаю, как это сделать, если не вставить исходный код. Вторая часть результата легко описывается следующим уравнением
snd . mapAccum acc = accumB acc . fmap (. snd)
Но первая часть не имеет такого приятного уравнения.
Я мог бы написать описание словами:
Функция
mapAccum
накапливает состояние типаacc
применяя функции, содержащиеся во втором аргументе типаEvent t (acc -> (x,acc))
, Функция возвращает событие, вхождения которого являются значениямиx
и поведение, которое отслеживает накопленное состояниеacc
, Иными словами, это машина Мили или государственный автомат.
но я не уверен, что эти слова действительно помогают.
Примечание: я использую ответ, чтобы писать отформатированный код
Вот моя попытка документирования функции:
mapAccum :: acc -- Initial Accumulation Value
-> Event t (acc -> (x, acc)) -- Event stream that of functions that use the previous
-- accumulation value to:
-- a) produce a new resulting event (x) and,
-- b) updates the accumulated value (acc)
-> (Event t x, Behavior t acc) -- fst) a stream of events from of a) above
-- snd) a behavior holding the accumulated values b) above
Эта функция является аналогом mapAccumL
функция от Data.List
модуль. Это эффективная комбинация accumE
а также accumB
, Результирующий Behavior
полезен, среди прочего, для хранения истории предыдущих событий, которые могут понадобиться для вычисления значений (x) потока событий.
Пример: вычисление скользящего среднего за последние 5 событий
rolingAverage :: forall t. Frameworks t => Event t Double -> Event t Double
rolingAverage inputStream = outputStream
where
funct x xs = (sum role / 5, role) where role = (x:init xs)
functEvent = funct <$> inputStream -- NB: curries the first parameter in funct
(outputStream,_) = mapAccum [0,0,0,0,0] functEvent