Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write common "if" branching in Haskell

Tags:

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?

like image 307
ondra Avatar asked Nov 28 '10 13:11

ondra


People also ask

What does ++ mean in Haskell?

The ++ operator is the list concatenation operator which takes two lists as operands and "combines" them into a single list.

What does apostrophe mean in Haskell?

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.

What does backslash mean in Haskell?

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, λ).

What does left arrow mean in Haskell?

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.


4 Answers

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

like image 126
sclv Avatar answered Nov 15 '22 09:11

sclv


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.

like image 36
luqui Avatar answered Nov 15 '22 11:11

luqui


A helper function can do this with pattern matching?

help x y
     where
     help (Just a) (Just b) = -- actions here ?
     help _        _        = return ()
like image 29
user268396 Avatar answered Nov 15 '22 10:11

user268396


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

like image 36
Yitz Avatar answered Nov 15 '22 09:11

Yitz