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. :)
精彩评论