反应香蕉:如何使用来自远程API的值并将它们合并到事件流中 [英] Reactive Banana: how to use values from a remote API and merge them in the event stream
问题描述
我在WX界面中使用Reactive-Banana。
当按下按钮时,我需要从外部服务API检索值。
我有一个通用的行为$根据数据类型
AppState
,根据函数转换( doSomeTransformation
)。被转换的值由事件传输,并且当按下界面上的按钮时,它们来自远程API( getRemoteValue
)。我写了代码的一个精简版本,它代表了基本部分:
module Main where
{ - #LANGUAGE ScopedTypeVariables# - } - 允许forall t。Moment t
import Graphics.UI.WX隐藏(事件)
导入无效。香蕉
导入Reactive.Banana.WX
{------------------------------ -----------------------------------------------
Main
-------------------------------------------- ----------------------------------}
data AppState = AppState {
count: :Int
}派生(显示)
类型String = [Char]
$ b $ main :: IO()
main = start $ do
f< - frame [text:=AppState]
myButton< - button f [text:=Go]
输出< - staticText f []
set f [layout:= margin 10 $
column 5 [widget myButton,widget output]]
let networkDescription :: forall t。框架t =>时刻t()
networkDescription = do
ebt< - event0 myButton命令
remoteValueB< - fromPoll getRemoteApiValue
myRemoteValue< - 更改remoteValueB
让
doSomeTransformation :: AppState - > AppState
doSomeTransformation ast = ast {count = count ast}
coreOfTheApp :: Behavior t AppState
coreOfTheApp = accumB initialState $(doSomeTransformation与myRemoteValue结合)< $ ebt
sink output [text:== show< $> coreOfTheApp]
network < - 编译networkDescription
启动网络
getRemoteApiValue :: IO Int
getRemoteApiValue =返回5
和cabal conf:
名称:brg
版本:0.1.0.0
简介:示例frp gui
- 说明:
许可证:PublicDomain
license-文件:LICENSE
作者:me
维护者:me@gmail.com
- 版权:
类别:fun
build-type:Simple
- - extra-source-files:
cabal-version:> = 1.10
可执行文件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
,转换器基础
,反应式香蕉> = 0.9&& < 0.10
,reactive-banana-wx == 0.9.0.2
hs-source-dirs:src
default-language:Haskell2010
ghc -options:-Wall -O2
我的问题在于如何编写 doSomeTransformation
和 myRemoteValue
,我可以将远程API值用作普通事件值。
更改
来自banana-reactive具有以下签名:
changes :: Frameworks t =>行为t a - > Moment t(Event t(Future a))
它会包裹我的 IO Int
from getRemoteApiValue
。
所以基本上我可以从: p>
IO Int - >时刻t(事件t(未来AppState)) - > AppState
?
顺便说一句确定它是否具有这种不同的功能签名:
doSomeTransformation :: Int - > AppState - > AppState
,其中 Int
值由API返回值表示。听起来像两个行为
s和一个流。也许是解决问题的一个不好的方法? 简短的回答: strong>转换函数需要接受另一个参数,即API的值:
$ b pre class =lang-hs prettyprint-override> transformState你需要使用
< $>
(即应用函数)而不是< $
(即用常数值覆盖):
accumB(AppState 0)$ transformState< $> remoteValueB< @ ebt
长答案:
注意:我已经重新命名/更改了一些内容,因此请详细阅读我的解释。
需要什么要改变的是你使用 accumB
折叠传入值的方式。 accumB
的工作方式是它应用了一系列函数 a - >一个
到一个种子值 a
,来计算 a
类型的最终值。您目前折叠API值的方式是始终将应用程序状态计数增量函数应用于初始状态,并完全丢弃传入值(使用< $
)。相反,您需要使用< $>
来映射传入值,而不是替换。你需要将价值映射到什么?一个函数(按照 accumB
的类型)!并且该函数是 transformValue eventValue :: AppState - >基于示例的列表和折叠:
hs prettyprint-override> * Frp> data State = State Int派生Show
* Frp>让变换x(状态c)=状态$ x + c
* Frp> let xs = [1,2,3,4,5] - API值
* Frp>让xsE = transform< $> xs :: [State - >州] - 事件流
* Frp> let accumB = foldr($)
* Frp> accumB(State 0)xsE
State 15
(不要忘记 现在考虑你目前如何覆盖来自 相反,你想要的是将传入的值映射到一些实际的函数,例如一个采用旧状态并将其组合到达值并产生一个新的状态值: 我也改变了 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 and the cabal conf: My problem here is how to compose which it will wrap my So basically how can I go from: ? BTW I am not sure if it is cleaner having this different function signature:
Short answer: the transform function needs to take one more argument, the value from the API: and you need to use 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 A lists and folds based example: (don't forget that Now consider how you are currently "overwriting" any events from 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
这篇关于反应香蕉:如何使用来自远程API的值并将它们合并到事件流中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋! a< $> b
与 fmap ab
相同,或者
$ b remoteValueB< @ ebt < (函数)常量
transformState
,这意味着所有到达的覆盖事件总是保持相同的内容: transformState
函数。
remoteValueE :: Event t Int
remoteValueE = remoteValueB< @ ebt
transformsE :: Event t(AppState - > AppState)
transformsE = transformState< $> remoteValueE
coreOfTheApp :: Behavior t AppState
coreOfTheApp = accumB initialState $ transformsE
getRemoteApiValue
来返回一个改变的值来模仿真实的API。因此,通过对代码进行一些修改,以下是一些可行的方法:
$ b import System.Random
类型RemoteValue = Int
- 在[0,10)
中生成一个随机值getRemoteApiValue :: IO RemoteValue
getRemoteApiValue =(`mod` 10 )< $> randomIO
$ b $ data AppState = AppState {count :: Int}派生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]
输出< - staticText f []
set f [layout:= minsize(sz 300 200)
$ margin 10
$第5列[widget myButton,widget output]]
let networkDescription :: forall t。框架t =>时刻t()
networkDescription = do
ebt< - event0 myButton命令
remoteValueB< - fromPoll getRemoteApiValue
myRemoteValue< - 更改remoteValueB
let
events = transformState< $> remoteValueB< @ ebt
coreOfTheApp :: Behavior t AppState
coreOfTheApp = accumB(AppState 0)events
sink output [text:== show< $ > coreOfTheApp]
network < - 编译networkDescription
启动网络
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
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
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))
IO Int
from getRemoteApiValue
.IO Int -> Moment t (Event t (Future AppState)) -> AppState
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?transformState v (AppState x) = AppState $ x + v
<$>
(i.e. apply function) instead of <$
(i.e. overwrite with constant value):accumB (AppState 0) $ transformState <$> remoteValueB <@ ebt
accumB
. The way accumB
works is that it applies a sequence of functions a -> a
to a seed value a
, to compute a final value of type a
. 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 of accumB
)! And that function is transformValue eventValue :: AppState -> AppState
.*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
a <$> b
is the same as fmap a b
, or just map a b
in the case of lists)remoteValueB <@ ebt
with the (function) constant transformState
, which means that all the overwritten events that arrive always hold the same content: the transformState
function.remoteValueE :: Event t Int
remoteValueE = remoteValueB <@ ebt
transformsE :: Event t (AppState -> AppState)
transformsE = transformState <$> remoteValueE
coreOfTheApp :: Behavior t AppState
coreOfTheApp = accumB initialState $ transformsE
getRemoteApiValue
to return a changing value to imitate a real API. So with some modifications to your code, here's something that works: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