这是使用liftM的情况吗?

时间:2012-08-19 10:00:30

标签: haskell

我有以下功能:

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的案例吗?我该如何正确应用?

3 个答案:

答案 0 :(得分:9)

liftM和朋友的目的是将函数升级为monadic设置,就像Applicative组合器一样。

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

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

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

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

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

monadic 操作,因此提升它将提供两层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 Enhancement支持idiom brackets,其中

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

这些Applicative mliftM家庭为monad做同样的工作,将f视为纯粹的n-ary函数和a1 .. {{1作为有效的参数。 an可以而且应该Monad,所以

Applicative

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

然后,该表示法允许您使用后缀(|(sendAllTo s datastring) (|SockAddrInet ioprot (inet_addr host)|)|) :: IO (IO ()) 来调用上述计算的monadic计算。

@

请注意,我仍然使用应用程序的纯前缀括号,因此模板的(|(sendAllTo s datastring) (|SockAddrInet ioprot (inet_addr host)|) @|) :: IO () 是整个f。符号允许您使用(sendAllTo s datastring)在任何位置标记纯参数,因此您可以编写此

~

如果心情带你。

Rant。我们花费了太多精力来确定正确的(|sendAllTo ~s ~datastring (|SockAddrInet ioprot (inet_addr host)|) @|) :: IO () liftMjoin=<<,{{1标点符号是为了解释如何在效果解释上下文(do这里)中将类型作为值解释内核(此处为(|...~...@|))。如果在类型中更明确地进行这种向上切割,我们应该在程序中需要更少的噪声来将值和计算分成对齐。我更倾向于使用计算机来推断()IO标记的位置,但事实上,Haskell类型的文档太模糊,无法实现。

答案 1 :(得分:4)

您应该可以使用<-这样

从IO monad中提取端口号
sendq s datastring host ioport = do
     hostAddr <- inet_addr host
     port     <- ioport
     sendAllTo s datastring (SockAddrInet port hostAddr)

答案 2 :(得分:3)

怎么样:

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

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

关键是只使用可以轻松解除的表达式部分,然后使用其他方式处理monad以形成其余的代码。这提供了简洁易读的代码。