2015-09-27 8 views
5

Używam Reactive-Banana w interfejsie WX. Po naciśnięciu przycisku muszę pobrać wartość z zewnętrznego interfejsu API usługi.Reactive Banana: jak używać wartości ze zdalnego interfejsu API i scalać je w strumieniu zdarzeń

Mam rodzajowy Behavior w oparciu o typ danych AppState, który "akumuluje" transformowane zmiany na podstawie transformacji funkcji (doSomeTransformation). Wartości przekształcane są transportowane przez zdarzenia i pochodzą ze zdalnego interfejsu API (getRemoteValue) po naciśnięciu przycisku na interfejsie. Pisałem szczupłą wersję kodu, który reprezentuje zasadniczy element:

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 

i Cabal conf:

name:    brg 
version:    0.1.0.0 
synopsis:   sample frp gui 
-- description: 
license:    PublicDomain 
license-file:  LICENSE 
author:    me 
maintainer:   [email protected] 
-- 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 

Mój problem polega na tym, jak komponować doSomeTransformation i myRemoteValue w taki sposób, że mogę użyj zdalnej wartości API jako normalnej wartości zdarzenia. changes z banana-reaktywnego ma następujący podpis:

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

który będzie zawijać moja IO Int z getRemoteApiValue.

Więc w zasadzie jak mogę iść z:

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

?

BTW Nie jestem pewien, czy jest czystszy o tej innej funkcji podpis: doSomeTransformation :: Int -> AppState -> AppState, gdzie wartość Int jest reprezentowana przez zwracaną wartość API. To brzmi jak dwa Behavior s i jeden strumień. Może zły sposób na rozwiązanie problemu?

+0

Co powiesz na samodzielne odtworzenie problemu? być może projekt FP Complete, lub przynajmniej pełny własny kod źródłowy. –

+0

Jedną z rzeczy, które staram się zrozumieć o Haskell i fp w ogóle, jest to, że czasami wystarczy podać sygnatury funkcji, aby opisać problem sam. W każdym razie postaram się napisać coś w fpcomplete. – Randomize

+1

Wystarczająco dużo, aby opisać problem, to nie to samo, co wystarczająca do odtworzenia problemu z piaskownicą. –

Odpowiedz

2

Krótka odpowiedź: funkcja przekształcać musi podjąć jeszcze jeden argument, wartość z API:

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

i trzeba użyć <$> (czyli funkcję zastosowanie) zamiast <$ (tj nadpisać o stałej wartości):

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

Długa odpowiedź:

Uwaga: Mam przemianowany/zmienił kilka rzeczy, więc proszę przeczytać moje wyjaśnienie odpowiednio

Co trzeba zmienić to sposób zagiąć wartości przychodzących z użyciem accumB. Sposób działania accumB polega na tym, że stosuje on sekwencję funkcji do obliczenia końcowej wartości typu a. Sposób, w jaki obecnie składasz wartości API, polega na tym, że zawsze stosuje się funkcję przyrostu stanu aplikacji do stanu początkowego, całkowicie odrzucając wartość przychodzącą (za pomocą <$). Zamiast tego musisz mapować wartość przychodzącą nie zastąpić go, używając <$>.Do czego chcesz zmapować wartość? Funkcja (zgodnie z typem accumB)! Ta funkcja to transformValue eventValue :: AppState -> AppState.

A wymieniono i fałdy przykład na bazie:

*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 

(nie zapominajcie, że a <$> b jest taka sama jak fmap a b, czy tylko map a b w przypadku list)

Obaczcież jesteś obecnie "nadpisywanie" dowolnych zdarzeń z remoteValueB <@ ebt ze stałą (funkcją) transformState, co oznacza, że ​​wszystkie nadpisane zdarzenia, które przychodzą, zawsze zawierają tę samą treść: funkcja transformState.

Zamiast tego, co chcesz, to do mapowania wartości przychodzących do niektórych rzeczywistych funkcji, na przykład taki, który bierze starego stanu i połączyć go do przybycia wartości i daje nową wartość Stan:

remoteValueE :: Event t Int 
remoteValueE = remoteValueB <@ ebt 

transformsE :: Event t (AppState -> AppState) 
transformsE = transformState <$> remoteValueE 

coreOfTheApp :: Behavior t AppState 
coreOfTheApp = accumB initialState $ transformsE 

I Zmieniłem także getRemoteApiValue, aby zwrócić zmienną wartość, aby naśladować prawdziwy interfejs API. Więc z pewnymi modyfikacjami twojego kodu, oto coś, co działa:

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 
+1

wow bardzo jasne wyjaśnienie- niestety mogę głosować tylko raz :) – Randomize

+0

bez probs; było to dla mnie również miłym ćwiczeniem! –

Powiązane problemy