Search code examples
haskellconditional-statementsbindmonadsdo-notation

How do I conditionally bind in a do block?


I want to achieve the following in a do block:

do 
  if condition then
    n0 <- expr0
  else
    n0 <- expr0'
  n1 <- expr1
  n2 <- expr2
  return T n0 n1 n2

But Haskell gives a compile error unless I do:

do 
  if condition then
    n0 <- expr0
    n1 <- expr1
    n2 <- expr2
    return T n0 n1 n2  
  else
    n0 <- expr0'
    n1 <- expr1
    n2 <- expr2
    return T n0 n1 n2 

It looks very verbose, especially when there are many shared binding expressions. How do I make it more concise?

Actually, I am trying to do the following:

do 
  if isJust maybeVar then
    n0 <- f (fromJust maybeVar)
    n1 <- expr1
    n2 <- expr2
    return (T (Just n0) n1 n2)
  else
    n1 <- expr1
    n2 <- expr2
    return (T Nothing n1 n2)

The following still fails to compile:

do 
  n0 <- if isJust maybeVar then Just (f (fromJust maybeVar)) else Nothing
  n1 <- expr1
  n2 <- expr2
  return (T n0 n1 n2)

Solution

  • You can "inline" the condition:

    do 
      n0 <- if condition then expr0 else expr0'
      n1 <- expr1
      n2 <- expr2
      return (T n0 n1 n2)

    You likely should use brackets in the return expression, so return (T n0 n1 n2).

    You can then rewrite the expression with liftM3 :: Monad m => (a1 -> a2 -> a3 -> r) -> m a1 -> m a2 -> m a3 -> m r to:

    liftM3 T (if condition then expr0 else expr0') expr1 expr2

    Since Haskell is a pure language evaluating expressions has no side-effects. But here the if...then...else will at most evaluate one of the two expression. An IO a itself has no side-effects either, since it is a "recipe" to generate an a.

    EDIT: For your second example, it is more complicated.

    do 
        n0 <- if isJust maybeVar then Just <$> (f (fromJust maybeVar)) else pure Nothing
        n1 <- expr1
        n2 <- expr2
        return (T n0 n1 n2)

    So here we use pure Nothing to "wrap" Nothing in the monadic context, and Just <$> to apply Just on the value(s) inside the monadic context.

    Or as @luqui says, we can here use traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b):

    do 
        n0 <- traverse f maybeVar
        n1 <- expr1
        n2 <- expr2
        return (T n0 n1 n2)

    This works since a Maybe is traversable: for a Just we traverse the single element, for Nothing it will return Nothing.