[英]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
命令,如readInputPin
和copyPin
,通過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
注意如何reactimate
和compile
只調用一次,主循環之外。 這些函數設置了您的事件網絡,您不希望在每個循環中調用它們。
最后,我們運行主循環。
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.