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
Другие вопросы по тегам