Как работает функция 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  
Другие вопросы по тегам