Do-notation and the list monad

1k views Asked by At

I am learning Haskell.

I am trying to find elements of a list as which sum to elements of a list bs, returning the elements as a tuple:

findSum2 :: [Int] -> [Int] -> [(Int,Int,Int)]
findSum2 as bs = [(a, a', b) | a <- as, a' <- as, b <- bs, a + a' == b]

The code works. But in order to learn Haskell, I'm trying to rewrite it as do-notation:

findSum2 :: [Int] -> [Int] -> [(Int,Int,Int)]
findSum2 as bs = do
  a  <- as
  a' <- as 
  b  <- bs
  if a + a' == b then return (a, a', b) 
                 else return ()

The type-checker then complains at me:

   • Couldn't match type ‘()’ with ‘(Int, Int, Int)’
      Expected type: [(Int, Int, Int)]
        Actual type: [()]

In all fairness, I knew it would. But since I can't skip the else clause in Haskell, what should I put in the return statement in the else clause?

Thanks.

2

There are 2 answers

0
willeM_ Van Onsem On

You can not return the unit (()), since that means that the return (a, a', b) and the return () have different types: the first one is [(Int, Int, Int)], whereas the latter is [()].

What you can do is use an empty list in case the guard fails, so:

findSum2 :: [Int] -> [Int] -> [(Int,Int,Int)]
findSum2 as bs = do
  a  <- as
  a' <- as 
  b  <- bs
  if a + a' == b then return (a, a', b) else []
1
4castle On

You must return something of the correct type in the else clause. It could be the empty list [], or one of the abstracted values like mzero or empty.

Or you could remove the if expression and use the guard function.

import Control.Monad (guard)

findSum2 :: [Int] -> [Int] -> [(Int,Int,Int)]
findSum2 as bs = do
  a  <- as
  a' <- as 
  b  <- bs
  guard (a + a' == b)
  return (a, a', b)

With this implementation you could now also generalize your function signature to:

findSum2 :: MonadPlus m => m Int -> m Int -> m (Int, Int, Int)