反应香蕉:如何使用来自远程API的值并将它们合并到事件流中 [英] Reactive Banana: how to use values from a remote API and merge them in the event stream

查看:136
本文介绍了反应香蕉:如何使用来自远程API的值并将它们合并到事件流中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我在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

(不要忘记 a< $> b fmap ab 相同,或者 map ab b
$ 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
启动网络


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 Behaviors 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:

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

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

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

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 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.

A lists and folds based example:

*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

(don't forget that a <$> b is the same as fmap a b, or just map a b in the case of lists)

Now consider how you are currently "overwriting" any events from remoteValueB <@ ebt with the (function) constant transformState, which means that all the overwritten events that arrive always hold the same content: the transformState 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:

remoteValueE :: Event t Int
remoteValueE = remoteValueB <@ ebt

transformsE :: Event t (AppState -> AppState)
transformsE = transformState <$> remoteValueE

coreOfTheApp :: Behavior t AppState
coreOfTheApp = accumB initialState $ transformsE

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:

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

这篇关于反应香蕉:如何使用来自远程API的值并将它们合并到事件流中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆