5

我在 WX 界面中使用 Reactive-Banana。按下按钮时,我需要从外部服务 API 检索值。

我有一个Behavior基于数据类型的泛型AppState,它基于函数转换(doSomeTransformation)“累积”转换后的变化。getRemoteValue转换后的值由事件传输,当按下界面上的按钮时,它们来自远程 API ( )。我编写了代表基本部分的代码的精简版本:

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以及如何myRemoteValue将远程 API 值用作普通事件值。 changes来自banana-reactive 的签名如下:

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

它将包装我IO IntgetRemoteApiValue

所以基本上我怎么能从:

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

?

顺便说一句,我不确定拥有这个不同的函数签名是否更清晰: doSomeTransformation :: Int -> AppState -> AppState,其中Int值由 API 返回值表示。听起来像是两个Behaviors 和一个流。也许是解决问题的坏方法?

4

1 回答 1

2

简短的回答:转换函数需要再接受一个参数,即来自 API 的值:

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

并且您需要使用<$>(即应用函数)而不是<$(即用常量值覆盖):

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

长答案:

注意:我已重命名/更改了一些内容,因此请相应地阅读我的解释

需要改变的是使用 . 折叠传入值的方式accumB。工作方式accumB是将一系列函数a -> a应用于种子值a,以计算类型的最终值a。您当前折叠 API 值的方式是始终将应用程序状态计数增量函数应用于初始状态,完全丢弃传入的值(通过使用<$)。相反,您需要映射传入的值而不是替换它,使用<$>. 您需要将值映射到什么?一个函数(根据 的类型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在列表的情况下)

现在考虑您当前如何remoteValueB <@ ebt使用 (function) 常量“覆盖”任何事件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。因此,通过对您的代码进行一些修改,以下是可行的:

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
于 2015-09-28T14:18:34.973 回答