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)
根本问题是一个概念问题:FRP 事件和行为只能以纯粹的方式组合。原则上,不可能有类型的函数,比如说
mapIO' :: (a -> IO b) -> Event a -> Event b
Run Code Online (Sandbox Code Playgroud)
因为相应的IO动作的执行顺序是未定义的。
在实践中,在组合事件和行为的同时执行 IO 有时可能很有用。组合execute
器可以做到这一点,如 @ErikAllik 所示。根据 的性质getAnotherRemoteApiValue
,这可能是正确的做法,特别是如果该函数是幂等的,或者从 RAM 中的位置进行快速查找。
但是,如果计算量较大,那么使用reactimate
IO 计算可能会更好。使用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)
)