Reactive Banana:使用对外部API的参数化调用

Ran*_*ize 6 haskell frp reactive-programming reactive-banana

从上一个问题开始: Reactive Banana:如何使用远程API中的值并将它们合并到事件流中

我现在有一个不同的问题:如何将Behaviour输出用作IO操作的输入并最终显示IO操作的结果?

以下是使用第二个输出更改上一个答案的代码:

import System.Random

type RemoteValue = Int

-- generate a random value within [0, 10)
getRemoteApiValue :: IO RemoteValue
getRemoteApiValue = (`mod` 10) <$> randomIO

getAnotherRemoteApiValue :: AppState -> IO RemoteValue
getAnotherRemoteApiValue state = (`mod` 10) <$> randomIO + count state

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 []
    output2  <- staticText f []

    set f [layout := minsize (sz 300 200)
                   $ margin 10
                   $ column 5 [widget myButton, widget output, widget output2]]

    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] 

          sink output2 [text :== show <$> reactimate ( getAnotherRemoteApiValue <@> coreOfTheApp)] 

    network <- compile networkDescription    
    actuate network
Run Code Online (Sandbox Code Playgroud)

正如你可以看到我想要做的是使用应用程序的新状态 - > getAnotherRemoteApiValue- > show.但它不起作用.

实际上有可能吗?

更新 基于Erik Allik和Heinrich Apfelmus以下答案,我有当前的代码情况 - 工作:):

{-# LANGUAGE ScopedTypeVariables #-}

module Main where

import System.Random
import Graphics.UI.WX hiding (Event, newEvent)
import Reactive.Banana
import Reactive.Banana.WX


data AppState = AppState { count :: Int } deriving Show

initialState :: AppState
initialState = AppState 0

transformState :: RemoteValue -> AppState -> AppState
transformState v (AppState x) = AppState $ x + v

type RemoteValue = Int

main :: IO ()
main = start $ do
    f        <- frame [text := "AppState"]
    myButton <- button f [text := "Go"]
    output1  <- staticText f []
    output2  <- staticText f []

    set f [layout := minsize (sz 300 200)
                   $ margin 10
                   $ column 5 [widget myButton, widget output1, widget output2]]

    let networkDescription :: forall t. Frameworks t => Moment t ()
        networkDescription = do    
          ebt <- event0 myButton command

          remoteValue1B <- fromPoll getRemoteApiValue

          let remoteValue1E = remoteValue1B <@ ebt

              appStateE = accumE initialState $ transformState <$> remoteValue1E
              appStateB = stepper initialState appStateE

              mapIO' :: (a -> IO b) -> Event t a -> Moment t (Event t b)
              mapIO' ioFunc e1 = do
                  (e2, handler) <- newEvent
                  reactimate $ (\a -> ioFunc a >>= handler) <$> e1
                  return e2

          remoteValue2E <- mapIO' getAnotherRemoteApiValue appStateE

          let remoteValue2B = stepper Nothing $ Just <$> remoteValue2E

          sink output1 [text :== show <$> appStateB] 
          sink output2 [text :== show <$> remoteValue2B] 

    network <- compile networkDescription    
    actuate network

getRemoteApiValue :: IO RemoteValue
getRemoteApiValue = do
  putStrLn "getRemoteApiValue"
  (`mod` 10) <$> randomIO

getAnotherRemoteApiValue :: AppState -> IO RemoteValue
getAnotherRemoteApiValue state = do
  putStrLn $ "getAnotherRemoteApiValue: state = " ++ show state
  return $ count state
Run Code Online (Sandbox Code Playgroud)

Hei*_*mus 3

根本问题是一个概念问题:FRP 事件和行为只能以纯粹的方式组合。原则上,不可能有类型的函数,比如说

mapIO' :: (a -> IO b) -> Event a -> Event b
Run Code Online (Sandbox Code Playgroud)

因为相应的IO动作的执行顺序是未定义的


在实践中,在组合事件和行为的同时执行 IO 有时可能很有用。组合execute器可以做到这一点,如 @ErikAllik 所示。根据 的性质getAnotherRemoteApiValue,这可能是正确的做法,特别是如果该函数是幂等的,或者从 RAM 中的位置进行快速查找。

但是,如果计算量较大,那么使用reactimateIO 计算可能会更好。使用newEvent创建AddHandler,我们可以给出该mapIO'函数的实现:

mapIO' :: (a -> IO b) -> Event a -> MomentIO (Event b)
mapIO' f e1 = do
    (e2, handler) <- newEvent
    reactimate $ (\a -> f a >>= handler) <$> e1
    return e2
Run Code Online (Sandbox Code Playgroud)

与纯组合器的主要区别

fmap :: (a -> b) -> Event a -> Event b
Run Code Online (Sandbox Code Playgroud)

后者保证输入和结果事件同时发生,而前者绝对不保证结果事件相对于网络中的其他事件何时发生。

请注意,这execute还保证输入和结果同时发生,但对允许的 IO 施加非正式限制。


reactimate通过这种与类似组合器组合的技巧,newEvent可以以类似的方式为行为编写。Reactive.Banana.Frameworks请记住,仅当您正在处理其精确顺序必然未定义的 IO 操作时,工具箱才适用。


(为了保持这个答案的最新性,我使用了即将到来的reactive-banana 1.0的类型签名。在版本0.9中,类型签名mapIO'

mapIO' :: Frameworks t => (a -> IO b) -> Event t a -> Moment t (Event t b)
Run Code Online (Sandbox Code Playgroud)