Reactive Banana: how to use values ​​from a remote API and combine them into an event stream

I am using Reactive-Banana in the WX interface. I need to get a value from an external service API when a button is clicked.

I have a generic Behavior based on an AppState data type that "accumulates" converted changes based on a function conversion ( doSomeTransformation ). The values ​​that are converted are carried by events, and they come from the remote API ( getRemoteValue ) when a button is clicked on the interface. I wrote a thin version of the code that represents the essential part:

 module Main where {-# LANGUAGE ScopedTypeVariables #-} -- allows "forall t. Moment t" import Graphics.UI.WX hiding (Event) import Reactive.Banana import Reactive.Banana.WX {----------------------------------------------------------------------------- Main ------------------------------------------------------------------------------} data AppState = AppState { count :: Int } deriving (Show) type String = [Char] main :: IO () main = start $ do f <- frame [text := "AppState"] myButton <- button f [text := "Go"] output <- staticText f [] set f [layout := margin 10 $ column 5 [widget myButton, widget output]] let networkDescription :: forall t. Frameworks t => Moment t () networkDescription = do ebt <- event0 myButton command remoteValueB <- fromPoll getRemoteApiValue myRemoteValue <- changes remoteValueB let doSomeTransformation :: AppState -> AppState doSomeTransformation ast = ast { count = count ast } coreOfTheApp :: Behavior t AppState coreOfTheApp = accumB initialState $ (doSomeTransformation to combine with myRemoteValue) <$ ebt sink output [text :== show <$> coreOfTheApp] network <- compile networkDescription actuate network getRemoteApiValue :: IO Int getRemoteApiValue = return 5 

and bondage conf:

 name: brg version: 0.1.0.0 synopsis: sample frp gui -- description: license: PublicDomain license-file: LICENSE author: me maintainer: me@gmail.com -- copyright: category: fun build-type: Simple -- extra-source-files: cabal-version: >=1.10 executable bgr main-is: Main.hs -- other-modules: -- other-extensions: build-depends: base >=4.7 && <4.8 , text , wx ==0.92.0.0 , wxcore ==0.92.0.0 , transformers-base , reactive-banana >=0.9 && <0.10 , reactive-banana-wx ==0.9.0.2 hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall -O2 

My problem is how to compose doSomeTransformation and myRemoteValue so that I can use the remote API value as the normal event value. changes from banana-reactive has the following signature:

 changes :: Frameworks t => Behavior ta -> Moment t (Event t (Future a)) 

which he will transfer my IO Int from getRemoteApiValue .

So basically, how can I go from:

 IO Int -> Moment t (Event t (Future AppState)) -> AppState 

?

By the way, I'm not sure if this is pure, having this different signature: doSomeTransformation :: Int -> AppState -> AppState , where the Int value is represented by the returned API value. It sounds like two Behavior and one stream. Maybe a bad way to solve the problem?

+5
source share
1 answer

Short answer: the transform function must take another argument, the value from the API:

 transformState v (AppState x) = AppState $ x + v 

and you need to use <$> (i.e. apply the function) instead of <$ (i.e. rewrite with constant value):

 accumB (AppState 0) $ transformState <$> remoteValueB <@ ebt 

Long answer:

Note. I renamed / changed a few things, so please read my explanations accordingly.

What needs to be changed is how you add up your values ​​with accumB . The method of accumB is that it applies a sequence of functions a -> a to the initial value of a to calculate the final value of type a . The way you currently collapse by API values ​​is to always apply the function to increase the application state counter to its original state, completely discarding the input value (using <$ ). Instead, you need to match the input value without replacing it using <$> . What do you need to match the value? Function (according to type accumB )! And this function transformValue eventValue :: AppState -> AppState .

An example based on lists and folds:

 *Frp> data State = State Int deriving Show *Frp> let transform x (State c) = State $ x + c *Frp> let xs = [1, 2, 3, 4, 5] -- the API values *Frp> let xsE = transform <$> xs :: [State -> State] -- the event stream *Frp> let accumB = foldr ($) *Frp> accumB (State 0) xsE State 15 

(do not forget that a <$> b matches fmap ab or just map ab in the case of lists)

Now consider how you are currently “rewriting” any events from remoteValueB <@ ebt the transformState function function, which means that all rewritten events always contain the same content: the transformState function.

Instead, you should match the input values ​​with some actual functions, for example, one that takes the old state and combines it with the received value and gives a new state value:

 remoteValueE :: Event t Int remoteValueE = remoteValueB <@ ebt transformsE :: Event t (AppState -> AppState) transformsE = transformState <$> remoteValueE coreOfTheApp :: Behavior t AppState coreOfTheApp = accumB initialState $ transformsE 

I also modified getRemoteApiValue to return a changing value to simulate a real API. Therefore, with some changes in your code, something works here:

 import System.Random type RemoteValue = Int -- generate a random value within [0, 10) getRemoteApiValue :: IO RemoteValue getRemoteApiValue = (`mod` 10) <$> randomIO data AppState = AppState { count :: Int } deriving Show transformState :: RemoteValue -> AppState -> AppState transformState v (AppState x) = AppState $ x + v main :: IO () main = start $ do f <- frame [text := "AppState"] myButton <- button f [text := "Go"] output <- staticText f [] set f [layout := minsize (sz 300 200) $ margin 10 $ column 5 [widget myButton, widget output]] let networkDescription :: forall t. Frameworks t => Moment t () networkDescription = do ebt <- event0 myButton command remoteValueB <- fromPoll getRemoteApiValue myRemoteValue <- changes remoteValueB let events = transformState <$> remoteValueB <@ ebt coreOfTheApp :: Behavior t AppState coreOfTheApp = accumB (AppState 0) events sink output [text :== show <$> coreOfTheApp] network <- compile networkDescription actuate network 
+2
source

All Articles