2012-09-08 4 views
6

J'ai regardé un certain nombre de réponses aux questions ici sur Stack Overflow essayant de trouver une solution à mon problème dans l'utilisation de la bibliothèque Reactive Banana. Toutes les réponses utilisent un magique en utilisant 'mapAccum' que je ne comprends pas très bien. En regardant la documentation API tout ce que je trouve est "Combinaison efficace de accumE et accumB." ce qui n'est pas très utile.Comment fonctionne la fonction mapAccum de Reactive Banana?

Il semble que cette fonction peut être utilisée pour comparer les valeurs d'un Behavior lors de deux événements consécutifs, ce que je voudrais faire. Mais je ne sais pas comment faire ce travail.

Comment fonctionne exactement mapAccum?

Répondre

4

Notez que

mapAccum :: acc -> Event t (acc -> (x, acc)) -> (Event t x, Behavior t acc) 

il prend une valeur initiale :: acc à accumuler sur, et un événement qui produit une fonction qui mises à jour valeur accumulée tout en produisant une valeur de sortie ::x. (En général, vous pouvez faire un tel événement en appliquant partiellement une fonction via <$>.) En conséquence, vous obtenez un nouvel événement qui déclenche vos valeurs x chaque fois qu'ils apparaissent et un comportement contenant votre valeur accumulée actuelle.

Utilisez mapAccum si vous avez un événement et que vous souhaitez y faire un comportement et un événement.

Par exemple, dans votre domaine de problème your other question, supposons que vous avez un événement eTime :: Event t Int qui a tiré de façon erratique et que vous vouliez calculer eDeltaTime :: Event t Int les différences et bTimeAgain :: Behaviour t Int pour le moment utilisé actuellement:

type Time = Int 
type DeltaTime = Time 

getDelta :: Time -> Time -> (DeltaTime,Time) 
getDelta new old = (new-old,new) 

Je pourrais avoir écrit que getDelta new = \old -> (new-old,new) pour faire l'étape suivante plus claire:

deltaMaker :: Event t (Time -> (DeltaTime,Time)) 
deltaMaker = getDelta <$> eTime 

(eDeltaT,bTimeAgain) = mapAccum 0 $ deltaMaker 

Dans ce cas, bTimeAgain serait b e un comportement ayant la même valeur que les événements dans eTime. Cela se produit parce que ma fonction getDelta passe new directement inchangé de eTime à la valeur acc. (Si je voulais bTimeAgain seul, j'aurais utilisé stepper :: a -> Event t a -> Behaviour t a.) Si je n'ai pas besoin de bTimeAgain, je pourrais juste écrire (eDeltaT,_) = mapAccum 0 $ deltaMaker.

4

La fonction mapAccum est très similaire à la fonction mapAccumL du module Data.List standard, d'où son nom. La documentation sur Hackage inclut également un lien vers le source code of mapAccum. Avec le type signature, il y a suffisamment d'informations pour comprendre comment fonctionne cette fonction.

Ensuite, je pourrais juste améliorer la documentation. :-) Mais je ne suis pas tout à fait clair sur la façon de faire cela à court de coller le code source. La deuxième partie du résultat est facile à décrire par l'équation suivante

snd . mapAccum acc = accumB acc . fmap (. snd) 

Mais la première partie ne dispose pas d'une telle équation agréable.

Je pourrais écrire une description dans les mots:

La fonction mapAccum accumule un état de type acc en appliquant les fonctions contenues dans le deuxième argument de type Event t (acc -> (x,acc)). La fonction renvoie un événement dont les occurrences sont les valeurs x et un comportement qui conserve la trace de l'état cumulé acc. Autrement dit, il s'agit d'une machine Mealy, ou automate d'état.

mais je ne suis pas sûr que ces mots aident réellement.

+0

Trois commentaires, 1) Je pense qu'il est grand que vous, l'auteur, sont ici répondre à des questions. 2) La notation sans points est difficile à lire si vous n'êtes pas sûr de ce que fait la fonction parce qu'il n'y a pas de noms. 3) Je me suis perdu en comprenant que le 'acc' donné à la fonction dans l'événement d'entrée était un bon endroit pour stocker l'histoire des événements. J'ai continué à essayer de mettre la valeur que j'essayais de trouver à la place, alors que cela aurait dû aller dans la partie «x». –

1

Remarque: J'utilise une réponse que je puisse écrire du code formaté

Voici ma tentative de documenter la fonction:


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 

Cette fonction est analogue à la mapAccumL fonction du module Data.List. C'est une combinaison efficace de accumE et accumB. Le résultat Behavior est utile, entre autres, pour conserver l'historique des événements précédents qui pourraient être nécessaires pour calculer les valeurs (x) d'un flux d'événements.

Exemple: calculer la moyenne roling des 5 derniers événements

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 
Questions connexes