Reactive Banana: как использовать значения из удаленного API и объединить их в потоке событий
Я использую Reactive-Banana в интерфейсе WX. Мне нужно получить значение из API внешнего сервиса при нажатии кнопки.
У меня есть общий Behavior
на основе типа данных AppState
что "накапливает" преобразованные изменения на основе преобразования функции (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
Моя проблема здесь в том, как сочинять doSomeTransformation
а также myRemoteValue
таким образом, что я могу использовать значение удаленного API в качестве обычного значения события.changes
Из банано-реактивных имеет следующую подпись:
changes :: Frameworks t => Behavior t a -> Moment t (Event t (Future a))
который обернет мой IO Int
от getRemoteApiValue
,
Так в принципе, как я могу перейти от:
IO Int -> Moment t (Event t (Future AppState)) -> AppState
?
Кстати, я не уверен, что это чище, если подпись этой другой функции:doSomeTransformation :: Int -> AppState -> AppState
, где Int
значение представлено возвращаемым значением API. Звучит как два Behavior
и один поток. Может плохой способ решить проблему?
1 ответ
Краткий ответ: функция преобразования должна получить еще один аргумент, значение из 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
с константой (функции) 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