Netwire 中的控制台交互性? [英] Console interactivity in Netwire?
问题描述
我正在使用 Netwire
haskell 库进行测试,并使其与简单的 time
连线一起工作:
I am testing with the Netwire
haskell library and made it work with a simple time
wire:
import Control.Wire
import Prelude hiding ((.), id)
import Control.Monad.IO.Class
import Data.Functor.Identity
import System.IO
wire :: (HasTime t s) => Wire s () m a t
wire = time
run :: (HasTime t s, MonadIO m, Show b, Show e) =>
Session m s -> Wire s e m a b -> m ()
run session wire = do
(dt, session') <- stepSession session
(wt', wire') <- stepWire wire dt $ Right undefined
case wt' of
-- | Exit
Left _ -> return ()
Right x -> do
liftIO $ do
putChar '
'
putStr $ either (ex -> show ex) show wt'
hFlush stdout
-- Interactivity here?
gotInput <- hReady stdin
if gotInput then
return ()
else return ()
run session' wire'
main :: IO ()
-- main = testWire clockSession_ wire
main = run clockSession_ wire
注意:run
基本上是由testWire
修改而来的,所以不知道是不是形成线网的正确方式.部分代码来自 http://todayincode.tumblr.com/post/96914679355/almost-a-netwire-5-tutorial 但该教程并未提及事件.
Note: the run
is basically modified from testWire
, so I don't know if it is the correct way to form a network of wires. Part of the code origin from http://todayincode.tumblr.com/post/96914679355/almost-a-netwire-5-tutorial but that tutorial does not say about events.
现在我正在尝试为程序添加一些交互性.现在,按任意键退出程序.我想我应该做一些事件切换.但是,我被困在这里,因为我找不到更改 wire'
或切换行为的方法.我试图阅读 API 文档和源代码,但我不知道如何实际触发"事件或使用它来切换线路.
Now I am trying to add a bit interactivity to the program. For now, quit the program when any key is pressed. I suppose I should do some event switching. However, I am stuck here because I cannot find a way to either change wire'
or switch the behaviour. I tried to read the API document and the source, but I don't see how to actually "fire" an Event or using it to switch the wire.
再说一次,由于我对 Haskell 还不是很熟悉,所以我可能在这里犯了一些大的愚蠢的错误.
Again, since I am not yet very familiar with Haskell, I may have made some big stupid mistakes here.
更新 1/2
我通过以下代码实现了我的目标.定时器在任何按键按下时停止.更新 2 我设法将 pollInput
分离成另一个 IO
唯一的函数,耶!
I got my goal working by the following code. The timer stops on any key press. Update 2 I managed to separate out pollInput
into another IO
only function, Yay!
import Control.Wire
import Prelude hiding ((.), id)
import Control.Monad.IO.Class
import Data.Functor.Identity
import System.IO
wire :: (HasTime t s) => Wire s () m a t
wire = time
run :: (HasTime t s, MonadIO m, Show b, Show e) =>
Session m s -> Wire s e m a b -> m ()
run session wire = do
-- Get input here
input <- liftIO $ pollInput
(dt, session') <- stepSession session
(wt', wire') <- stepWire wire dt $ input
case wt' of
-- | Exit
Left _ -> liftIO (putStrLn "") >> return ()
Right x -> do
liftIO $ do
putChar '
'
putStr $ either (ex -> show ex) show wt'
hFlush stdout
run session' wire'
pollInput :: IO (Either a b)
pollInput = do
gotInput <- hReady stdin
if gotInput then
return (Left undefined)
else return (Right undefined)
setup :: IO ()
setup = do
hSetBuffering stdin NoBuffering
hSetBuffering stdout NoBuffering
main :: IO ()
main = do
setup
run clockSession_ wire
然而,这引发了一些进一步的问题.首先,这是好的做法吗?第二,pollInput
的类型是什么?我试图手动输入它但没有成功.不过,自动类型推导是有效的.
However, this raises some further questions. First, is this good practise? Second, what is the type of pollInput
? I tried to manually type it out but without success. Automatic type deduction works, though.
这是我对这段代码如何工作的解释:
This is my explanation of how this code works:
首先,来自控制台的用户输入被轮询,经过一些逻辑之后,生成输入"到连线(名称选择不当,但生成的输入是连线输入)并沿网络传递.在这里,我只是传递了一个抑制(Left something
),并且会导致循环退出.当然,退出时,程序会产生一个换行符,让控制台看起来更好看.
First, the user input from console is polled, and after some logic, the "input" to wire is generated (poor name choice, but that input generated is the wire input) and passed along the network. Here, I simply pass an inhibition (Left something
), and will cause the loop to exit. Of course, when exiting, the program produces a newline to make console look nicer.
(不过,我还是不明白 Event
是如何工作的)
(Well, I still don't understand how Event
works, though)
更新 3/4
阅读@Cirdec 的回答后,在我的编辑器上做了很多调整,我得到了这个没有 IORef
的单线程版本,也退出了按 'x'Update 4: (但它不输出任何东西):
After reading @Cirdec 's answer, and fiddled a lot on my editor, I get this single threaded version without IORef
, also quitting on pressing 'x'Update 4: (but it does not output anything):
import Control.Wire
import Prelude hiding ((.),id)
import Control.Wire.Unsafe.Event
import System.IO
import Control.Monad.IO.Class
data InputEvent = KeyPressed Char
| NoKeyPressed
deriving (Ord, Eq, Read, Show)
type OutputEvent = IO ()
--- Wires
example :: (HasTime t s, Monad m, Show t) =>
Wire s () m (Event [InputEvent]) (Event [OutputEvent])
example = switch $
(fmap ((:[]) . print) <$> periodic 1 . time
&&&
fmap (const mkEmpty) <$> filterE (any (== KeyPressed 'x'))
)
readKeyboard :: IO (Either e (InputEvent))
readKeyboard = do
hSetBuffering stdin NoBuffering
gotInput <- hReady stdin
if gotInput then do
c <- getChar
return $ Right $ KeyPressed c
else return $ Right $ NoKeyPressed
output :: [OutputEvent] -> IO ()
output (x:xs) = id x >> output xs
output _ = return ()
run :: (HasTime t s, MonadIO m) =>
Session m s -> Wire s e m (Event [InputEvent]) (Event [OutputEvent]) -> m e
run = go
where
go session wire = do
-- | inputEvent :: Event InputEvent
inputEvent <- liftIO $ readKeyboard
(dt, session') <- stepSession session
(wt', wire') <- stepWire wire dt (Event <$> (fmap (:[]) inputEvent))
-- (wt', wire') <- stepWire wire dt (Right undefined)
case wt' of
Left a -> return a
Right bEvent -> do
case bEvent of
Event b -> liftIO $ output b
_ -> return ()
go session' wire'
main = do
run clockSession_ example
我认为这比我原来的要好得多,但我仍然不完全相信这是否是好的做法.
I think this is much better than my original, but I am still not completely convinced whether it is good practise or not.
推荐答案
首先,我要指出 Netwire 5 中的 Kleisli Arrow?.在尝试理解 Monads 和 Arrows 的很长一段时间后,我想出了这个答案.我将很快使用 Kleisli Wire 举一个最小的例子.
First, I would point to Kleisli Arrow in Netwire 5?. I came up with that answer after a longggg time of trying to understand Monads and Arrows. I will put a minimal example using Kleisli Wire soon.
这个程序只是回显用户输入的内容,并在遇到q
时退出.虽然没用,但它展示了使用 Netwire 5 的一个可能的好习惯.
This program merely echos what the user types, and quits when it hits a q
. Though useless, it demonstrates a probably good practice of using Netwire 5.
mkKleisli :: (Monad m, Monoid e) => (a -> m b) -> Wire s e m a b
mkKleisli f = mkGen_ $ a -> liftM Right $ f a
这是在引用的帖子的答案中编写的 Kleisli 线构造函数.总之,这个函数提升了任何 Kleisli 函数 a ->;m b
到 Wire s e m a b
.这是我们在这个程序中所做的任何 I/O 的核心.
This is the Kleisli wire constructor written in the answer in the post referenced. In summary, this function lifts any Kleisli function a -> m b
into Wire s e m a b
. This is the core about any I/O we are doing in this program.
由于我们作为用户类型回显,hGetChar
可能是最好的选择.因此,我们将其提升为金属丝.
Since we are echoing as user types, hGetChar
is probably the best choice. Therefore, we lift that into a wire.
inputWire :: Wire s () IO () Char
inputWire = mkKleisli $ \_ -> hGetChar stdin
同样,我们使用下面的线在屏幕上输出字符.
Similarly, we use the following wire to output characters on screen.
outputWire :: Wire s () IO Char ()
outputWire = mkKleisli $ putChar
然后为了确定我们什么时候需要退出,当q
为输入时,构造一个纯wire输出True
(注意mkSF_
可以用来代替arr
).
Then to determine when we need to quit, a pure wire is constructed to output True
when q
is the input (Note that mkSF_
can be used instead of arr
).
quitWire :: (Monad m, Monoid e) => Wire s e m Char Bool
quitWire = arr $ quitNow
where
quitNow c
| c == 'q' || c == 'Q' = True
| otherwise = False
要实际使用退出信息,我们需要编写一个特殊的(但非常简单)runWire
函数,该函数运行Wire s e m () Bool
类型的连线.当连线被禁止或返回false时,函数结束.
To actually use the information of quitting, we need to write a special (but really simple) runWire
function which runs a wire of type Wire s e m () Bool
. When the wire is inhibited or returns false, the function ends.
runWire :: (Monad m) => Session m s -> Wire s e m () Bool -> m ()
runWire s w = do
(ds, s') <- stepSession s
(quitNow, w') <- stepWire w ds (Right ())
case quitNow of
Right False -> runWire s' w'
_ -> return ()
现在,让我们把电线放在一起.
Now, let's put wires together.
mainWire = inputWire >>> (quitWire &&& outputWire) >>> arr ((q,_) -> q)
当然我们可以使用箭头语法:
Of course we can use the Arrow syntax:
mainWire = proc _ -> do
c <- inputWire -< ()
q <- quitWire -< c
outputWire -< c
returnA -< q
不确定 proc
版本是否更快,但在这个简单的例子中,两者都非常可读.
Not sure if the proc
version is faster or not, but in this simple example, both are quite readable.
我们从 inputWire
获取输入,将它提供给 quitWire
和 outputWire
并得到一个元组 (Bool, ())代码>.然后我们将第一个作为最终输出.
We get input from inputWire
, feed it to both quitWire
and outputWire
and get a tuple (Bool, ())
. Then we take the first one as the final output.
最后,我们在main
中运行所有东西!
At last, we run everything in main
!
main = do
hSetEcho stdin False
hSetBuffering stdin NoBuffering
hSetBuffering stdout NoBuffering
runWire clockSession_ mainWire
这是我使用的最终代码:
Here comes the final code I used:
{-# LANGUAGE Arrows #-}
module Main where
import Control.Wire
import Control.Monad
import Control.Arrow
import System.IO
import Prelude hiding ((.), id)
mkKleisli :: (Monad m, Monoid e) => (a -> m b) -> Wire s e m a b
mkKleisli f = mkGen_ $ a -> liftM Right $ f a
inputWire :: Wire s () IO () Char
inputWire = mkKleisli $ \_ -> hGetChar stdin
outputWire :: Wire s () IO Char ()
outputWire = mkKleisli $ putChar
quitWire :: (Monad m, Monoid e) => Wire s e m Char Bool
quitWire = arr $ quitNow
where
quitNow c
| c == 'q' || c == 'Q' = True
| otherwise = False
runWire :: (Monad m) => Session m s -> Wire s e m () Bool -> m ()
runWire s w = do
(ds, s') <- stepSession s
(quitNow, w') <- stepWire w ds (Right ())
case quitNow of
Right False -> runWire s' w'
_ -> return ()
mainWire = inputWire >>> (quitWire &&& outputWire) >>> arr ((q,_) -> q)
main = do
hSetEcho stdin False
hSetBuffering stdin NoBuffering
hSetBuffering stdout NoBuffering
runWire clockSession_ mainWire
这篇关于Netwire 中的控制台交互性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!