控制台在Netwire中的交互性? [英] Console interactivity in Netwire?
问题描述
我正在使用 Netwire
haskell库进行测试,并使用简单的时间
连线:
import Control.Wire
导入前导隐藏((。),id)
导入控制。 Monad.IO.Class
import Data.Functor.Identity
import System.IO
wire ::(HasTime ts)=> Wire s()m a t
wire = time
run ::(HasTime t s,MonadIO m,Show b,Show e)=>
Session m s - > Wires e m a b - > m()
run session wire = do
(dt,session')< - stepSession session
(wt',wire')< - stepWire wire dt $ Right undefined
案例wt'为
- |退出
向左_ - > return()
Right x - >做
liftIO $ do
putChar'\r'
putStr $ $(\ex - > show ex)show wt'
hFlush stdout
- 交互性在这里?
gotInput< - hReady stdin
如果gotInput然后
返回()
else返回()
运行会话'wire'
main :: IO()
- main = testWire clockSession_ wire
main = run clockSession_ wire
注意: run
基本上是从 testWire
修改的,所以我不知道它是不是正确的方式来形成电线网络。部分代码来自 http://todayincode.tumblr.com / post / 96914679355 / almost-a-netwire-5-tutorial ,但该教程并未提及事件。
现在我试着添加一些与程序的交互性。现在,当任何按键被按下时退出程序。我想我应该做一些事件切换。然而,我被困在这里,因为我找不到改变 wire'
或切换行为的方法。我尝试阅读API文档和源代码,但是我不知道如何实际触发事件或使用它来切换线路。
同样,因为我对Haskell还不是很熟悉,所以我可能在这里犯了一些很愚蠢的错误。
更新1/2
。计时器停止任何按键。 Update 2 我设法将 pollInput
分隔为另一个 IO
函数,Yay!
import Control.Wire
导入前导隐藏((。),id)
导入Control.Monad.IO.Class
import Data.Functor.Identity
import System.IO
wire ::(HasTime ts)=> Wire s()m a t
wire = time
run ::(HasTime t s,MonadIO m,Show b,Show e)=>
Session m s - > Wires e m a b - > m()
run session wire = do
- 在此处获得输入
input< - liftIO $ pollInput
(dt,session')< - stepSession
-
(wt',wire')< - stepWire wire dt $ input
case wt'退出
向左_ - > liftIO(putStrLn)>> return()
Right x - > do
liftIO $ do
putChar'\r'
putStr $ $(\ex - > show ex)show wt'
hFlush stdout
run session'wire'
pollInput :: IO(或者ab)
pollInput = do
gotInput< - hReady stdin
如果gotInput则
返回(左未定义)
else返回(右未定义)
setup :: IO()
setup =
hSetBuffering stdin NoBuffering
hSetBuffering stdout NoBuffering
$ b $ main :: IO()
main = do
setup
run clockSession_ wire
然而,这引发了一些进一步的问题。首先,这是一个好的做法吗? 其次, pollInput
的类型是什么?我试图手动输入,但没有成功。自动型扣除工程,但。
这是我对这段代码如何工作的解释:
首先,用户输入控制台被轮询,并且在一些逻辑之后,生成线路的输入被生成(不良的名称选择,但生成的输入是线路输入)并且沿着网络传递。在这里,我只是通过一个抑制( Left something
),并且会导致循环退出。
(呃,我还是不明白 Event
works)
更新3/4
阅读@Cirdec的回答,并在编辑器上弄了很多,我得到这个没有 IORef
的单线程版本,也退出了按下'x' Update 4 :(但它不会输出任何内容):
import Control.Wire
import Prelude隐藏((。),id)
导入Control.Wire.Unsafe.Event
导入System.IO
导入Control.Monad.IO.Class
data InputEvent = KeyPressed Char
| NoKeyPressed
导出(Ord,Eq,Read,Show)
类型OutputEvent = IO()
---导线
示例::(HasTime ts,Monad m ,Show t)=>
Wire s()m(Event [InputEvent])(Event [OutputEvent])
example = switch $
(fmap((:[])。print)< $> periodic 1.时间
&&
fmap(const mkEmpty)< $> filterE(any(== KeyPressed'x'))
)
readKeyboard :: IO(e(InputEvent))
readKeyboard = do
hSetBuffering stdin NoBuffering
gotInput< - hReady stdin
if gotInput then do
c< b ; - getChar
return $ Right $ KeyPressed c
else return $ Right $ NoKeyPressed
output :: [OutputEvent] - > IO()
output(x:xs)= id x>>输出xs
output _ = return()
run ::(HasTime t s,MonadIO m)=>
Session m s - > Wire s e m(Event [InputEvent])(Event [OutputEvent]) - > m
run = go
其中
去会话线= do
- | inputEvent :: Event InputEvent
inputEvent< - liftIO $ readKeyboard
(dt,session')< - stepSession session
(wt',wire')< - stepWire wire dt < $>(fmap(:[])inputEvent))
- (wt',wire')< - stepWire wire dt(右未定义)
case wt'
左侧 - >返回
Right bEvent - >做
案例b $
事件b - > liftIO $输出b
_ - > return()
go session'wire'
$ b $ main = do
run clockSession_ example
我认为这比我原来的要好得多,但我仍然不完全相信这是否是好的做法。
首先,我会指向 Netwire 5中的Kleisli Arrow?。在尝试了解Monads和Arrows之后,我想出了这个答案。
这个程序仅回应用户输入的内容,当它点击<$时退出C $ C> q 。虽然没用,但它展示了使用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的核心。
由于我们以用户类型的形式回显, hGetChar
可能是最好的选择。因此,我们将其提升为线。
inputWire :: Wire s()IO()Char
inputWire = mkKleisli $ \_ - > hGetChar stdin
同样,我们使用以下连线在屏幕上输出字符。
outputWire :: Wire s()IO Char()
outputWire = mkKleisli $ putChar
然后确定什么时候我们需要退出,当<$ $时,纯电线构造成输出 True
c $ c> q 是输入(请注意,可以使用 mkSF _
来代替 arr
>)。
quitWire ::(Monad m,Monoid e)=> Wire s e m Char Bool
quitWire = arr $ quitNow
其中
quitNow c
| c =='q'|| c =='Q'= True
|否则= False
要实际使用退出信息,我们需要编写一个特殊的) runWire
函数,它运行类型为 Wire的sem()Bool
。
runWire ::(Monad m)=>当线路被禁止或返回false时,函数结束。会话m s - > Wire s e m()Bool - > m()
runWire sw = do
(ds,s')< - stepSession s
(quitNow,w')< - stepWire w ds(Right())
案例quit
正确False - > runWire s'w'
_ - >返回()
现在,我们将电线放在一起。
mainWire = inputWire>>> (quitWire&& outputWire)>>> arr(\(q,_) - > q)
当然,我们可以使用箭头语法:
mainWire = proc _ - > do
c< - inputWire - < ()
q < - quitWire - < c
outputWire - < c
returnA - < q
不知道 最后,我们在 下面是我使用的最终代码: I am testing with the Note: the 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 Again, since I am not yet very familiar with Haskell, I may have made some big stupid mistakes here. Update 1/2 I got my goal working by the following code. The timer stops on any key press. Update 2 I managed to separate out However, this raises some further questions. First, is this good practise? This is my explanation of how this code works: 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 ( (Well, I still don't understand how Update 3/4 After reading @Cirdec 's answer, and fiddled a lot on my editor, I get this single threaded version without I think this is much better than my original, but I am still not completely convinced whether it is good practise or not. 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. This program merely echos what the user types, and quits when it hits a This is the Kleisli wire constructor written in the answer in the post referenced. In summary, this function lifts any Kleisli function Since we are echoing as user types, Similarly, we use the following wire to output characters on screen. Then to determine when we need to quit, a pure wire is constructed to output To actually use the information of quitting, we need to write a special (but really simple) Now, let's put wires together. Of course we can use the Arrow syntax: Not sure if the We get input from At last, we run everything in Here comes the final code I used:
这篇关于控制台在Netwire中的交互性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋! proc
版本是我们从 inputWire
获取输入,feed它到> quitWire
和 outputWire
并获得一个元组(Bool,())
。然后我们把第一个作为最终的输出。
$ b main
!
main = do
hSetEcho stdin False
hSetBuffering stdin NoBuffering
hSetBuffering stdout NoBuffering
runWire clockSession_ mainWire
{ - #LANGUAGE箭头# - }
模块Main其中
导入Control.Wire
导入Control.Monad
导入Control.Arrow
导入System.IO
导入前导隐藏((。),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
其中
quitNow c
| c =='q'|| c =='Q'= True
|否则= False
runWire ::(Monad m)=>会话m s - > Wire s e m()Bool - > m()
runWire sw = do
(ds,s')< - stepSession s
(quitNow,w')< - stepWire w ds(Right())
案例quit
正确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
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 '\r'
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
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'
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.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 '\r'
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
Second, what is the type of pollInput
? I tried to manually type it out but without success. Automatic type deduction works, though. Left something
), and will cause the loop to exit. Of course, when exiting, the program produces a newline to make console look nicer.Event
works, though)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 will put a minimal example using Kleisli Wire soon.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
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
is probably the best choice. Therefore, we lift that into a wire.inputWire :: Wire s () IO () Char
inputWire = mkKleisli $ \_ -> hGetChar stdin
outputWire :: Wire s () IO Char ()
outputWire = mkKleisli $ putChar
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
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 ()
mainWire = inputWire >>> (quitWire &&& outputWire) >>> arr (\(q,_) -> q)
mainWire = proc _ -> do
c <- inputWire -< ()
q <- quitWire -< c
outputWire -< c
returnA -< q
proc
version is faster or not, but in this simple example, both are quite readable.inputWire
, feed it to both quitWire
and outputWire
and get a tuple (Bool, ())
. Then we take the first one as the final output.main
!main = do
hSetEcho stdin False
hSetBuffering stdin NoBuffering
hSetBuffering stdout NoBuffering
runWire clockSession_ mainWire
{-# 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