简体   繁体   中英

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 trying 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. :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM