I am using Reactive-Banana in a WX interface. I need to retrieve a value from an external service API when a button is pressed.
I have a generic Behavior
based on the data type AppState
that “accums” the transformed changes based on a function transformation (doSomeTransformation
). The values that get transformed are transported by the events and they come from a remote API (getRemoteValue
) when a button on the interface is pressed. I have written a slim 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 the cabal 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 here is how to compose doSomeTransformation
and myRemoteValue
in a way that I can use the remote API value as normal event value.
changes
from banana-reactive has the following signature:
changes :: Frameworks t => Behavior t a -> Moment t (Event t (Future a))
which it will wrap my IO Int
from getRemoteApiValue
.
So basically how can I go from:
IO Int -> Moment t (Event t (Future AppState)) -> AppState
?
BTW I am not sure if it is cleaner having this different function signature:
doSomeTransformation :: Int -> AppState -> AppState
, where the Int
value is represented by the API returned value. It sounds like two Behavior
s and one stream. Maybe a bad way to solve the problem?
Short answer: the transform function needs to take one more argument, the value from the API:
and you need to use
<$>
(i.e. apply function) instead of<$
(i.e. overwrite with constant value):Long answer:
Note: I've renamed/changed a few things so please read my explanation accordingly
What needs to be changed is the way you fold over the incoming values using
accumB
. The wayaccumB
works is that it applies a sequence of functionsa -> a
to a seed valuea
, to compute a final value of typea
. The way you are currently folding over the API values is by always applying the app state count increment function to the initial state, completely throwing away the incoming value (by using<$
). Instead you need to map the incoming value not replace it, using<$>
. What do you need to map the value to? A function (as per the type ofaccumB
)! And that function istransformValue eventValue :: AppState -> AppState
.A lists and folds based example:
(don't forget that
a <$> b
is the same asfmap a b
, or justmap a b
in the case of lists)Now consider how you are currently "overwriting" any events from
remoteValueB <@ ebt
with the (function) constanttransformState
, which means that all the overwritten events that arrive always hold the same content: thetransformState
function.Instead, what you want is to map the incoming values to some actual functions, for example one that takes the old state and combine it to the arrived value and yields a new state value:
I've also changed
getRemoteApiValue
to return a changing value to imitate a real API. So with some modifications to your code, here's something that works: