3

我有以下功能:

sendq :: Socket -> B.ByteString -> String -> IO PortNumber -> IO ()
sendq s datastring host port = do
     hostAddr <- inet_addr host
     sendAllTo s datastring (SockAddrInet port hostAddr)

而 sendAllTo 具有函数签名

sendAllTo :: Socket -> ByteString -> SockAddr -> IO ()

问题是从以前的函数中我得到了一个 IO PortNumber,其中 SockAddr 只接受一个 PortNumber。我试图通过将 sendAllTo 提升到 IO monad 来使这两者兼容:

liftM sendAllTo s datastring (SockAddrInet port hostAddr)

但没有快乐。告诉我一些关于许多争论的事情。这是liftM的情况吗?如何正确应用它?

4

3 回答 3

9

liftM和朋友的目的是将函数提升到一元设置,就像Applicative组合器所做的那样。

liftM :: Monad m => (s -> t) -> m s -> m t

您尝试的代码有两个问题。一是缺少括号。

liftM sendAllTo :: IO Socket -> IO (ByteString -> SockAddr -> IO ())

这不是你的意思。另一个问题是

sendAllTo :: Socket -> ByteString -> SockAddr -> IO ()

是一个单子操作,所以提升它会提供两层IO. 通常的方法是把应用程序的纯前缀括起来,像这样

liftM (sendAllTo s datastring) :: IO SockAddr -> IO (IO ())

然后,您可以使用liftM2.

liftM2 SockAddrInet ioport (inet_adder host) :: IO SockAddr

那给你

liftM (sendAllTo s datastring) (liftM2 SockAddrInet ioport (inet_adder host))
  :: IO (IO ())

就目前而言,这将一无所获,因为它解释了如何计算操作,但实际上并没有调用它!这就是你需要的地方

join (liftM (sendAllTo s datastring) (liftM2 SockAddrInet ioport (inet_addr host)))
  :: IO ()

或者,更紧凑

sendAllTo s datastring =<< liftM2 SockAddrInet ioport (inet_adder host)

插头。Strathclyde Haskell 增强支持成语方括号,其中

(|f a1 .. an|) :: m t如果f :: s1 -> ... -> sn -> ta1 :: m s1... an :: m sn_

它们的作用与家庭对 monad 的作用相同Applicative mliftMf其视为纯 n 元函数,并将a1..an视为有效参数。Monads 可以也应该是Applicative,所以

(|SockAddrInet ioprot (inet_addr host)|) :: IO SockAddr

(|(sendAllTo s datastring) (|SockAddrInet ioprot (inet_addr host)|)|) :: IO (IO ())

然后,该表示法允许您调用像上面这样的计算单子计算,后缀为@.

(|(sendAllTo s datastring) (|SockAddrInet ioprot (inet_addr host)|) @|) :: IO ()

请注意,我仍然将应用程序的纯前缀括起来,因此f模板的(sendAllTo s datastring). 该符号允许您在任何位置用 a 标记纯参数~,因此您可以这样写

(|sendAllTo ~s ~datastring (|SockAddrInet ioprot (inet_addr host)|) @|) :: IO ()

如果心情带你。

咆哮。我们花费太多精力来找出正确的liftM, join, =<<, do,(|...~...@|)标点符号,以便解释如何()在效果解释上下文(此处)中将类型切割为值解释内核(IO此处)。如果这种向上切割在类型中更明确,我们应该在程序中需要更少的噪音来将值和计算对齐。我更喜欢计算机来推断~@标记的去向,但就目前情况而言,Haskell 类型的文档太模糊,无法实现。

于 2012-08-19T11:45:32.723 回答
4

您应该能够<-像这样从 IO monad 中提取端口号

sendq s datastring host ioport = do
     hostAddr <- inet_addr host
     port     <- ioport
     sendAllTo s datastring (SockAddrInet port hostAddr)
于 2012-08-19T10:22:17.380 回答
3

关于什么:

sendq s datastring host ioport = (SockAddrInet <$> inet_addr host <*> ioport)
     >>= sendAllTo s datastring

我们使用liftM(实际上,我更喜欢,<$>但它们是同一件事)来构造SockAddrInet然后使用标准>>=

关键是只对表达式中容易被提升的部分使用提升,然后使用其他处理 monad 的方式来形成其余的代码。这给出了简洁易读的代码。

于 2012-08-19T11:46:46.090 回答