Haskell STM 不输出或 deriving Show 不提供任何输出

huangapple go评论57阅读模式
英文:

Haskell STM not giving output or deriving Show not giving any output

问题

我正在用Haskell编写一个Telnet控制台,使用以下代码:

module Main (main) where

import qualified Data.ByteString.Char8 as C

import Control.Concurrent (forkFinally)
import qualified Control.Exception as E
import Control.Monad (unless, forever, void)
import Control.Monad.STM (STM, atomically)
import Control.Concurrent.STM.TVar (TVar, newTVar, writeTVar, readTVar)

import qualified Data.ByteString as S
import Network.Socket (close, setSocketOption)
import Network.Socket (HostName)
import Network.Socket (ServiceName, AddrInfo)
import Network.Socket (Socket, SocketType(Stream))
import Network.Socket (addrFlags, getAddrInfo, gracefulClose, accept, listen, addrAddress, bind, setCloseOnExecIfNeeded, openSocket, withFdSocket, SocketOption(ReuseAddr))
import Network.Socket (addrSocketType, defaultHints, AddrInfoFlag(AI_PASSIVE))
import Network.Socket.ByteString (recv, sendAll)

data Störm = An | Aus deriving Show

someFunc :: IO ()
someFunc = runTCPServer Nothing "3000" (talk $ newTVar Aus)
  where
    talk :: STM (TVar Störm) -> Socket -> IO ()
    talk state' s = recv s 1024 >>= \msg -> unless (S.null msg) $
      case C.unpack msg of
        "exit\r\n" -> sendAll s (C.pack "Auf Widerhoren")
        "an\r\n" -> sendAll s (C.pack "Am An\n") >> putStrLn "Am An" >> write state' An >> talk state' s
        "aus\r\n" -> sendAll s (C.pack "Am Aus\n") >> putStrLn "Am Aus" >> write state' Aus >> talk state' s
        "anzeigen\r\n" -> sendAll s <$> (atomically $ C.pack <$> show <$> (state' >>= readTVar)) >> putStrLn "Am Anzeigen State" >> talk state' s
        otherwise -> sendAll s (C.pack "Unbekannte Befehl\n") >> putStrLn (show msg) >> (talk (state') s)
    write :: STM (TVar Störm) -> Störm -> IO ()
    write state new = atomically $ state >>= \s -> writeTVar s new

runTCPServer :: Maybe HostName -> ServiceName -> (Socket -> IO a) -> IO a
runTCPServer mhost port server = resolve >>= \addr -> (E.bracket (open addr) close loop)
  where
    resolve :: IO AddrInfo
    resolve = do
        let hints = defaultHints {
                addrFlags = [AI_PASSIVE]
              , addrSocketType = Stream
              }
        head <$> getAddrInfo (Just hints) mhost (Just port)

    open :: AddrInfo -> IO Socket
    open addr = E.bracketOnError (openSocket addr) close $ \sock -> do
        setSocketOption sock ReuseAddr 1
        withFdSocket sock setCloseOnExecIfNeeded
        bind sock $ addrAddress addr
        listen sock 1024
        return sock

    loop :: Socket -> IO b0
    loop sock = forever $ E.bracketOnError (accept sock) (close . fst)
        $ \(conn, _peer) -> void $
            -- 'forkFinally' alone is unlikely to fail thus leaking @conn@,
            -- but 'E.bracketOnError' above will be necessary if some
            -- non-atomic setups (e.g. spawning a subprocess to handle
            -- @conn@) before proper cleanup of @conn@ is your case
            forkFinally (server conn) (const $ gracefulClose conn 5000)

它使用以下包:

base ^>=4.16.4.0
, network ^>=3.1.4.0
, bytestring
, stm

当从Telnet会话连接到控制台并运行anaus,然后运行anzeigen,或者直接连接到控制台时,控制台不会返回相应的AnAus消息给Telnet客户端,而只会返回一个空格。同时,我知道选择是正确的,因为服务器的stdout中显示了消息“Am Anzeigen State”。

我不知道为什么会发生这种情况。

英文:

I am writing a telnet console in haskell with this code

module Main (main) where

import qualified Data.ByteString.Char8 as C

import Control.Concurrent (forkFinally)
import qualified Control.Exception as E
import Control.Monad (unless, forever, void)
import Control.Monad.STM (STM, atomically)
import Control.Concurrent.STM.TVar (TVar, newTVar, writeTVar, readTVar)

import qualified Data.ByteString as S
import Network.Socket (close, setSocketOption)
import Network.Socket (HostName)
import Network.Socket (ServiceName, AddrInfo)
import Network.Socket (Socket, SocketType(Stream))
import Network.Socket (addrFlags, getAddrInfo, gracefulClose, accept, listen, addrAddress, bind, setCloseOnExecIfNeeded, openSocket, withFdSocket, SocketOption(ReuseAddr))
import Network.Socket (addrSocketType, defaultHints, AddrInfoFlag(AI_PASSIVE))
import Network.Socket.ByteString (recv, sendAll)

data Störm = An | Aus deriving Show

someFunc :: IO ()
someFunc = runTCPServer Nothing "3000" (talk $ newTVar Aus)
  where
    talk :: STM (TVar Störm) -> Socket -> IO ()
    talk state' s = recv s 1024 >>= \msg -> unless (S.null msg) $
      case C.unpack msg of
        "exit\r\n" -> sendAll s (C.pack "Auf Widerhoren")
        "an\r\n" -> sendAll s (C.pack "Am An\n") >> putStrLn "Am An" >> write state' An >> talk state' s
        "aus\r\n" -> sendAll s (C.pack "Am Aus\n") >> putStrLn "Am Aus" >> write state' Aus >> talk state' s
        "anzeigen\r\n" -> sendAll s <$> (atomically $ C.pack <$> show <$> (state' >>= readTVar)) >> putStrLn "Am Anzeigen State" >> talk state' s
        otherwise -> sendAll s (C.pack "Unbekannte Befehl\n") >> putStrLn (show msg) >> (talk (state') s)
    write :: STM (TVar Störm) -> Störm -> IO ()
    write state new = atomically $ state >>= \s -> writeTVar s new

runTCPServer :: Maybe HostName -> ServiceName -> (Socket -> IO a) -> IO a
runTCPServer mhost port server = resolve >>= \addr -> (E.bracket (open addr) close loop)
  where
    resolve :: IO AddrInfo
    resolve = do
        let hints = defaultHints {
                addrFlags = [AI_PASSIVE]
              , addrSocketType = Stream
              }
        head <$> getAddrInfo (Just hints) mhost (Just port)

    open :: AddrInfo -> IO Socket
    open addr = E.bracketOnError (openSocket addr) close $ \sock -> do
        setSocketOption sock ReuseAddr 1
        withFdSocket sock setCloseOnExecIfNeeded
        bind sock $ addrAddress addr
        listen sock 1024
        return sock

    loop :: Socket -> IO b0
    loop sock = forever $ E.bracketOnError (accept sock) (close . fst)
        $ \(conn, _peer) -> void $
            -- 'forkFinally' alone is unlikely to fail thus leaking @conn@,
            -- but 'E.bracketOnError' above will be necessary if some
            -- non-atomic setups (e.g. spawning a subprocess to handle
            -- @conn@) before proper cleanup of @conn@ is your case
            forkFinally (server conn) (const $ gracefulClose conn 5000)

which uses the packages

base ^>=4.16.4.0
, network ^>=3.1.4.0
, bytestring
, stm

When connect to the telnet to the console from a telnet session, and run an or aus and then anzeigen or just directly the console not returns the corresponding An or Aus message to the telnet client but only an space, meanwhile I know that the case is being properly selected because the message Am Anzeigen State is being showed in the server's stdout.

I have not idea why this is happening.

答案1

得分: 2

问题在于你没有发送任何内容。

在你的 "anzeigen" 处理程序中,你有这段代码(将 partThatDoesntMatter = (atomically $ C.pack <$> show <$> (state' >>= readTVar)) 重命名为:

sendAll s <$> partThatDoesntMatter >> putStrLn "Am Anzeigen State"

这只是上面代码的一个替代写法:

fmap (sendAll s) partThatDoesntMatter >> putStrLn "Am Anzeigen State"

你想要在这里使用 sendAll 发送一些内容,但是你这里的 fmap 调用只是产生一个 sendAll 操作,它永远不会被执行(并且立即被之后的 >> 丢弃)。

你需要的是:

partThatDoesntMatter >>= sendAll s >> putStrLn "Am Anzeigen State"

比较一下:

fmap :: (a -> b) -> m a -> m b
(>>=) :: m a -> (a -> m b) -> m b

应用到我们的例子上,这就是:

sendAll s :: ByteString -> IO ()
partThatDoesntMatter :: IO ByteString
fmap (sendAll s) partThatDoesntMatter :: IO (IO ())
partThatDoesntMatter >>= sendAll s :: IO ()
英文:

The problem is that you're not sending anything.

In your "anzeigen" handler, you have this code (renaming partThatDoesntMatter = (atomically $ C.pack &lt;$&gt; show &lt;$&gt; (state&#39; &gt;&gt;= readTVar))

sendAll s &lt;$&gt; partThatDoesntMatter &gt;&gt; putStrLn &quot;Am Anzeigen State&quot; 

This just an alternate spelling of

fmap (sendAll s) partThatDoesntMatter &gt;&gt; putStrLn &quot;Am Anzeigen State&quot;

You want to send something using sendAll here, but your fmap call here is just producing a sendAll action that never gets executed (and immediately discarded by the &gt;&gt; afterwards).

What you need here is

partThatDoesntMatter &gt;&gt;= sendAll s &gt;&gt; putStrLn &quot;Am Anzeigen State&quot;

Compare:

fmap :: (a -&gt; b) -&gt; m a -&gt; m b
(&gt;&gt;=) :: m a -&gt; (a -&gt; m b) -&gt; m b

applied to our example this is the difference between

sendAll s :: ByteString -&gt; IO ()
partThatDoesntMatter :: IO ByteString
fmap (sendAll s) partThatDoesntMatter :: IO (IO ())
partThatDoesntMatter &gt;&gt;= sendAll s :: IO ()

huangapple
  • 本文由 发表于 2023年8月10日 19:41:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76875403.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定