开发者

How to write common "if" branching in Haskell

I have the following snippet of code:

srcaddr <- getIfaceAddr iface >>= inet_ntoa . fromJust 
dstaddr <- getDestAddr iface >>= inet_ntoa . fromJust 
-- I want to perform actions only if neither getIfaceAddr 
-- nor getDestAddr returned Nothing
action1 srcaddr dstaddr
action2 srcaddr dstaddr
action3 srcaddr dstaddr

getIfaceAddr :: String -> IO (Maybe HostAddress)
getDestAddr :: String -> IO (Maybe HostAddress)

How to write this code in 'nice Haskell'? I was thinking about the MaybeT monad but somehow wasn't able make it work. I was tr开发者_JAVA百科ying to do some 'lifting', but wasn't able to stich the types together. I can change the signature of the getIfaceAddr/getDestAddr.

As a sidenote: why is inet_ntoa 'HostAddress -> IO String'? I don't think there are any side effects, are they?


Another, helperless solution:

msrcaddr <- getIfaceAddr iface >>= traverse inet_ntoa
mdstaddr <- getDestAddr iface >>= traverse inet_ntoa
case liftM2 (,) msrcaddr mdstaddr of
   Just (srcaddr,dstaddr) ->
      action1 srcaddr dstaddr
      action2 srcaddr dstaddr
      action3 srcaddr dstaddr
   Nothing -> return ()

You can also replace the case with a maybe, if you prefer. Or you can avoid the liftM2 by just pattern matching out of the pair directly.

Edit: Here's a link to the documentation for Traversable, an overlooked but frequently indispensable typeclass: http://haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/Data-Traversable.html


Oh my, what is that fromJust? If getIfaceAddr returns Nothing, this code will crash your program.

The MaybeT solution looks like this:

srcaddr <- lift . inet_ntoa =<< MaybeT (getIfaceAddr iface)
dstaddr <- lift . inet_ntoa =<< MaybeT (getDestAddr iface)
lift $ do
    action1 srcaddr dstaddr
    ...

The types for the first line fit together like this:

getIfaceAddr iface          :: IO (Maybe HostAddress)
MaybeT (getIfaceAddr iface) :: MaybeT IO HostAddress
inet_ntoa                   :: HostAddress -> IO String
lift . inet_ntoa            :: HostAddress -> MaybeT IO String
lift . inet_ntoa =<< MaybeT (getIfaceAddr iface)
                            :: MaybeT IO String

Remember that your code is going to have type MaybeT IO something, so you have to runMaybeT to get it back into IO before binding it to main.


A helper function can do this with pattern matching?

help x y
     where
     help (Just a) (Just b) = -- actions here ?
     help _        _        = return ()


You can write it as an "if-branching" like this:

import Control.Monad (when)
import Data.Maybe (isJust)

...
  mSrcaddr <- fmap inet_ntoa $ getIfaceAddr iface
  mDstaddr <- fmap inet_ntoa $ getDestAddr iface
  when (isJust mSrcaddr && isJust mDstaddr) $ do
    let Just srcaddr = mSrcaddr
        Just dstaddr = mDstaddr
    action1 srcaddr dstaddr
    action2 srcaddr dstaddr
    action3 srcaddr dstaddr

But I don't like being in the bad habit of writing the kinds of pattern matches that could potentially fail and crash my program, even though in this case it is safe.

Also, I don't like using isJust and friends and manually testing; the Maybe type already means "something that could fail", and there are built-in functions which allow us to preserve that meaning while using Maybe values.

So I would probably write it like this:

import Control.Applicative (liftA2)
import Data.Maybe (fromMaybe)

...
  mSrcaddr <- fmap inet_ntoa $ getIfaceAddr iface
  mDstaddr <- fmap inet_ntoa $ getDestAddr iface
  fromMaybe (return ()) $ liftA2 doActions mSrcaddr mDstaddr
where
  doActions srcaddr dstaddr = do
      action1 srcaddr dstaddr
      action2 srcaddr dstaddr
      action3 srcaddr dstaddr

Yeah, I know, a helper function. Sorry, that's how I'd actually write it in real life. :)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜