簡體   English   中英

在重新啟動內執行MonadIO操作

[英]Execute MonadIO action inside of reactimate

在reactive-banana中,我試圖運行reactimate :: Event (IO ()) -> Moment () ,其中包含一些Arduino動作,在hArduino包中 ,是MonadIO一個實例。 似乎沒有Arduino a -> IO a功能在包中提供。 你如何在reactimate執行Arduino行動?

我沒有使用Arduino或hArduino的經驗,所以請用一小撮鹽來做。

鑒於這是不合理的重新初始化每個板reactimate ,我不認為這是一個干凈的選項[*]。 最根本的問題是,實施reactimate在無功香蕉不知道什么的Arduino單子,所以它增加了額外的所有特效的時候一定已經解決reactimate觸發的動作(因此IO型)。 我能看到的唯一出路就是滾動自己的withArduino版本,跳過初始化。 從在快速瀏覽 ,這看起來是可行的,如果很凌亂。

[*]或者至少是一個不涉及可變狀態的干凈選項,如正確的答案。


鑒於Heinrich Apfelmus通過提出一個有趣的方法來善意地增加這個答案,我忍不住實施他的建議。 也歸功於gelisam,因為他的答案的腳手架為我節省了相當多的時間。 除了代碼塊下面的注釋,請參閱Heinrich的博客 ,了解有關“叉車”的額外評論。

{-# LANGUAGE GeneralizedNewtypeDeriving, ScopedTypeVariables #-}

import Control.Monad (join, (<=<), forever)
import Control.Concurrent
import Data.Word
import Text.Printf
import Text.Read (readMaybe)
import Reactive.Banana
import Reactive.Banana.Frameworks

main :: IO ()
main = do
    let inputPin  = pin 1
        outputPin = pin 2

        readInputPin = digitalRead inputPin
        copyPin = digitalWrite outputPin =<< readInputPin

    ard <- newForkLift withArduino

    (lineAddHandler, fireLine) <- newAddHandler

    let networkDescription :: forall t. Frameworks t => Moment t ()
        networkDescription = do
            eLine <- fromAddHandler lineAddHandler

            let eCopyPin = copyPin <$ filterE ("c" ==) eLine
                eReadInputPin = readInputPin <$ filterE ("i" ==) eLine

            reactimate $ (printf "Input pin is on? %s\n" . show <=< carry ard)
                <$> eReadInputPin
            reactimate $ carry ard
                <$> eCopyPin

    actuate =<< compile networkDescription

    initialised <- newQSem 0
    carry ard $ liftIO (signalQSem initialised)
    waitQSem initialised

    forever $ do
        putStrLn "Enter c to copy, i to read input pin."
        fireLine =<< getLine

-- Heinrich's forklift.

data ForkLift m = ForkLift { requests :: Chan (m ()) }

newForkLift :: MonadIO m
            => (m () -> IO ()) -> IO (ForkLift m)
newForkLift unlift = do
    channel <- newChan
    let loop = forever . join . liftIO $ readChan channel
    forkIO $ unlift loop
    return $ ForkLift channel

carry :: MonadIO m => ForkLift m -> m a -> IO a
carry forklift act = do
    ref <- newEmptyMVar
    writeChan (requests forklift) $ do
        liftIO . putMVar ref =<< act
    takeMVar ref

-- Mock-up lifted from gelisam's answer.
-- Please pretend that Arduino is abstract.

newtype Arduino a = Arduino { unArduino :: IO a }
  deriving (Functor, Applicative, Monad, MonadIO)

newtype Pin = Pin Word8

pin :: Word8 -> Pin
pin = Pin

digitalWrite :: Pin -> Bool -> Arduino ()
digitalWrite (Pin n) v = Arduino $ do
    printf "Pretend pin %d on the arduino just got turned %s.\n"
           n (if v then "on" else "off")

digitalRead :: Pin -> Arduino Bool
digitalRead p@(Pin n) = Arduino $ do
    printf "We need to pretend we read a value from pin %d.\n" n
    putStrLn "Should we return True or False?"
    line <- getLine
    case readMaybe line of
        Just v -> return v
        Nothing -> do
            putStrLn "Bad read, retrying..."
            unArduino $ digitalRead p

withArduino :: Arduino () -> IO ()
withArduino (Arduino body) = do
    putStrLn "Pretend we're initializing the arduino."
    body

筆記:

  • 叉車(這里, ard )在一個單獨的線程中運行一個Arduino循環。 carry允許我們發送Arduino命令,如readInputPincopyPin ,通過Chan (Arduino ())在這個線程中執行。

  • 它只是一個名稱,但無論如何, newForkLift被稱為unlift的參數很好地反映了上面的討論。

  • 通信是雙向的。 carry工藝品MVar ,讓我們訪問Arduino命令返回的值。 這允許我們以完全自然的方式使用eReadInputPin事件。

  • 這些層干凈地分開。 一方面,主循環僅觸發像eLine這樣的UI事件,然后由事件網絡處理。 另一方面, Arduino代碼僅通過叉車與事件網絡和主循環通信。

  • 我為什么要在那里放一個sempahore 我會告訴你如果你把它拿走會發生什么......

你如何在reactimate執行Arduino行動?

我會通過執行具有可觀察副作用的IO操作來間接執行它們。 然后,在withArduino ,我會觀察到這個副作用並運行相應的Arduino命令。

這是一些示例代碼。 首先,讓我們完成進口。

{-# LANGUAGE GeneralizedNewtypeDeriving, ScopedTypeVariables #-}

import Control.Monad.IO.Class
import Data.IORef
import Data.Word
import Reactive.Banana
import Reactive.Banana.Frameworks
import Text.Printf

由於我沒有arduino,我將不得不模擬hArduino中的一些方法。

newtype Arduino a = Arduino (IO a)
  deriving (Functor, Applicative, Monad, MonadIO)

newtype Pin = Pin Word8

pin :: Word8 -> Pin
pin = Pin

digitalWrite :: Pin -> Bool -> Arduino ()
digitalWrite (Pin n) v = Arduino $ do
    printf "Pretend pin %d on the arduino just got turned %s.\n"
           n (if v then "on" else "off")

digitalRead :: Pin -> Arduino Bool
digitalRead (Pin n) = Arduino $ do
    printf "We need to pretend we read a value from pin %d.\n" n
    putStrLn "Should we return True or False?"
    readLn

withArduino :: Arduino () -> IO ()
withArduino (Arduino body) = do
    putStrLn "Pretend we're initializing the arduino."
    body

在其余的代碼中,我假裝Arduino和Pin類型是不透明的。

我們需要一個事件網絡來將表示從arduino接收的信號的輸入事件轉換為描述我們想要發送給arduino的輸出事件。 為了使事情變得非常簡單,讓我們從一個引腳接收數據並在另一個引腳上輸出完全相同的數據。

eventNetwork :: forall t. Event t Bool -> Event t Bool
eventNetwork = id

接下來,讓我們將我們的活動網絡連接到外部世界。 當輸出事件發生時,我只是將值寫入IORef,稍后我將能夠觀察到它。

main :: IO ()
main = do
    (inputPinAddHandler, fireInputPin) <- newAddHandler
    outputRef <- newIORef False

    let networkDescription :: forall t. Frameworks t => Moment t ()
        networkDescription = do
            -- input
            inputPinE <- fromAddHandler inputPinAddHandler

            -- output
            let outputPinE = eventNetwork inputPinE

            reactimate $ writeIORef outputRef <$> outputPinE
    network <- compile networkDescription
    actuate network

    withArduino $ do
      let inputPin  = pin 1
      let outputPin = pin 2

      -- initialize pins here...

      -- main loop
      loop inputPin outputPin fireInputPin outputRef

注意如何reactimatecompile只調用一次,主循環之外。 這些函數設置了您的事件網絡,您不希望在每個循環中調用它們。

最后,我們運行主循環。

loop :: Pin
     -> Pin
     -> (Bool -> IO ())
     -> IORef Bool
     -> Arduino ()
loop inputPin outputPin fireInputPin outputRef = do
    -- read the input from the arduino
    inputValue <- digitalRead inputPin

    -- send the input to the event network
    liftIO $ fireInputPin inputValue

    -- read the output from the event network
    outputValue <- liftIO $ readIORef outputRef

    -- send the output to the arduino
    digitalWrite outputPin outputValue

    loop inputPin outputPin fireInputPin outputRef

請注意我們如何使用liftIO與Arduino計算中的事件網絡進行交互。 我們呼吁fireInputPin觸發輸入事件,該事件引起網絡響應觸發的輸出事件,以及writeIORef我們送給reactimate導致輸出事件的值寫入到IOREF。 如果事件網絡更復雜並且輸入事件未觸發任何輸出事件,則IORef的內容將保持不變。 無論如何,我們可以觀察內容,並使用它來確定運行哪個Arduino計算。 在這種情況下,我們只需將輸出值發送到預定的引腳。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM