Haskell : non-symmetric nested do-notation and let

Solution 1:

The two branches of an if must have the same type. If you need one with a monadic type and the other with a "pure" type, you need to make those types equal using a return (or, equivalently, pure).

Here's an example using the IO monad. You can adapt it to your case.

main :: IO ()
main = do
   putStrLn "enter True of False"
   x <- readLn   
   res <- if x
      then do
         putStrLn "You entered True: write somehting now"
         getLine             -- its result is bound to res
      else return "nothing"  -- res is bound to "nothing"
   putStrLn $ "final result: " ++ res

Main points:

  • If a branch of your if contains an action that you want to run right now, you must avoid let res = if ... and instead use res <- if .... The former defines res to be the action itself, the latter executes the action and defines res to be the result produced by the action.
  • Both braches of res <- if ... must have the same monadic type. Use return to make it so.

There is no way to use the standard if with a monadic then branch and a non-monadic else branch. At best, we can define a custom "if" function that does that, e.g.

ifMon :: Monad m => Bool -> m a -> a -> m a
ifMon True x  _ = x
ifMon False _ y = return y

This can be used as

do
   putStrLn "blah"
   res <- ifMon condition
          (do
              putStrLn "then branch"
              return "abc")
          "else result, non monadic"

You can have a similar helper function for the case where the then branch is "pure" and the else branch is monadic.