Reactive Banana:如何使用来自远程 API 的值并将它们合并到事件流中
Reactive Banana: how to use values from a remote API and merge them in the event stream
我在 WX 界面中使用 Reactive-Banana。
我需要在按下按钮时从外部服务 API 检索一个值。
我有一个基于数据类型 AppState
的通用 Behavior
,它基于函数转换 (doSomeTransformation
) “累积”转换后的变化。转换的值由事件传输,当按下界面上的按钮时,它们来自远程 API (getRemoteValue
)。我写了一个精简版的代码,代表了本质部分:
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
我的问题是如何以可以将远程 API 值用作正常事件值的方式组合 doSomeTransformation
和 myRemoteValue
。
来自 banana-reactive 的 changes
具有以下签名:
changes :: Frameworks t => Behavior t a -> Moment t (Event t (Future a))
它将从 getRemoteApiValue
.
包装我的 IO Int
所以基本上我该怎么做:
IO Int -> Moment t (Event t (Future AppState)) -> AppState
?
顺便说一句,我不确定具有这种不同的函数签名是否更清晰:
doSomeTransformation :: Int -> AppState -> AppState
,其中 Int
值由 API 返回值表示。听起来像是两个 Behavior
和一个流。也许解决问题的方法不好?
简答: 转换函数需要再接受一个参数,值来自 API:
transformState v (AppState x) = AppState $ x + v
并且您需要使用 <$>
(即应用函数)而不是 <$
(即用常量值覆盖):
accumB (AppState 0) $ transformState <$> remoteValueB <@ ebt
长答案:
注意:我renamed/changed一些事情,所以请相应地阅读我的解释
需要更改的是使用 accumB
折叠传入值的方式。 accumB
的工作方式是将一系列函数 a -> a
应用于种子值 a
,以计算类型为 a
的最终值。您当前折叠 API 值的方式是始终将应用程序状态计数增量函数应用于初始状态,完全丢弃传入值(通过使用 <$
)。相反,您需要 map 传入值而不是 replace 它,使用 <$>
。您需要将值映射到什么?一个函数(根据 accumB
的类型)!该函数是 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
与 fmap a b
相同,或者在列表的情况下只是 map a b
)
现在考虑一下您当前如何 "overwriting" 来自 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
更改为 return 一个不断变化的值以模仿真实的 API。因此,通过对您的代码进行一些修改,以下是有效的方法:
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
我在 WX 界面中使用 Reactive-Banana。 我需要在按下按钮时从外部服务 API 检索一个值。
我有一个基于数据类型 AppState
的通用 Behavior
,它基于函数转换 (doSomeTransformation
) “累积”转换后的变化。转换的值由事件传输,当按下界面上的按钮时,它们来自远程 API (getRemoteValue
)。我写了一个精简版的代码,代表了本质部分:
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
我的问题是如何以可以将远程 API 值用作正常事件值的方式组合 doSomeTransformation
和 myRemoteValue
。
来自 banana-reactive 的 changes
具有以下签名:
changes :: Frameworks t => Behavior t a -> Moment t (Event t (Future a))
它将从 getRemoteApiValue
.
IO Int
所以基本上我该怎么做:
IO Int -> Moment t (Event t (Future AppState)) -> AppState
?
顺便说一句,我不确定具有这种不同的函数签名是否更清晰:
doSomeTransformation :: Int -> AppState -> AppState
,其中 Int
值由 API 返回值表示。听起来像是两个 Behavior
和一个流。也许解决问题的方法不好?
简答: 转换函数需要再接受一个参数,值来自 API:
transformState v (AppState x) = AppState $ x + v
并且您需要使用 <$>
(即应用函数)而不是 <$
(即用常量值覆盖):
accumB (AppState 0) $ transformState <$> remoteValueB <@ ebt
长答案:
注意:我renamed/changed一些事情,所以请相应地阅读我的解释
需要更改的是使用 accumB
折叠传入值的方式。 accumB
的工作方式是将一系列函数 a -> a
应用于种子值 a
,以计算类型为 a
的最终值。您当前折叠 API 值的方式是始终将应用程序状态计数增量函数应用于初始状态,完全丢弃传入值(通过使用 <$
)。相反,您需要 map 传入值而不是 replace 它,使用 <$>
。您需要将值映射到什么?一个函数(根据 accumB
的类型)!该函数是 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
与 fmap a b
相同,或者在列表的情况下只是 map a b
)
现在考虑一下您当前如何 "overwriting" 来自 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
更改为 return 一个不断变化的值以模仿真实的 API。因此,通过对您的代码进行一些修改,以下是有效的方法:
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