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?
The ++ operator is the list concatenation operator which takes two lists as operands and "combines" them into a single list.
That apostrophe doesn't have any special meaning in Haskell's syntax. It's a valid character to use in a function name. We usually use ' to either denote a strict version of a function (one that isn't lazy) or a slightly modified version of a function or a variable.
In Haskell, the backslash is used both to introduce special characters and to introduce lambda functions (since it is a reasonable approximation in ASCII of the Greek letter lambda, λ).
The left arrow gets used in do notation as something similar to variable binding, in list comprehensions for the same (I'm assuming they are the same, as list comprehensions look like condensed do blocks), and in pattern guards, which have the form (p <- e). All of those constructs bind a new variable.
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. :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With