haskellmonadsmonadplus

Trouble understanding error handling with monads


I'm trying to build a function which returns me the single element from a list. The list is part of a Maybe (Int,[Int]) tupel.

If the list contains no elements, I want to return an error. If the list contains exactly 1 element, I want to return that element as a Monad. If the list contains more than 1 element, I want to return an error.

I'm a bit lost and cannot see how to make this rather simple thing work. Here is what I have so far:

import Control.Monad

test1 = Just (1,[2,3]) :: Maybe (Int,[Int])
test2 = Just (2,[1]) :: Maybe (Int,[Int])
test3 = Just (3,[]) :: Maybe (Int,[Int])

getValue :: Maybe Bool -> Bool
getValue (Just x) = x
getValue Nothing = False

singleElemOnly :: (MonadPlus m) => [a] -> m a
singleElemOnly x = let result = test2
                       value = fmap fst result
                       isEmpty = fmap null (fmap snd result)
                   in if (getValue isEmpty) then value else mzero

Unfortunately the error messages I am getting when trying to compile this are of absolutely no use to me as a beginner..

Playground.hs:15:50:
    Could not deduce (a ~ Int)
    from the context (MonadPlus m)
      bound by the type signature for
                 singleElemOnly :: MonadPlus m => [a] -> m a
      at Playground.hs:11:19-45
      `a' is a rigid type variable bound by
          the type signature for singleElemOnly :: MonadPlus m => [a] -> m a
          at Playground.hs:11:19
    Expected type: m a
      Actual type: Maybe Int
    Relevant bindings include
      x :: [a]
        (bound at Playground.hs:12:16)
      singleElemOnly :: [a] -> m a
        (bound at Playground.hs:12:1)
    In the expression: value
    In the expression: if (getValue isEmpty) then value else mzero

Playground.hs:15:50:
    Could not deduce (m ~ Maybe)
    from the context (MonadPlus m)
      bound by the type signature for
                 singleElemOnly :: MonadPlus m => [a] -> m a
      at Playground.hs:11:19-45
      `m' is a rigid type variable bound by
          the type signature for singleElemOnly :: MonadPlus m => [a] -> m a
          at Playground.hs:11:19
    Expected type: m a
      Actual type: Maybe Int
    Relevant bindings include
      singleElemOnly :: [a] -> m a
        (bound at Playground.hs:12:1)
    In the expression: value
    In the expression: if (getValue isEmpty) then value else mzero

Any help much appreciated!


Solution

  • I will translate from your specification:

    If the list contains no elements, I want to return an error.

    f [] = mzero
    

    If the list contains exactly 1 element, I want to return that element as a Monad.

    f [x] = return x
    

    If the list contains more than 1 element, I want to return an error.

    f (_:_:_) = mzero
    

    So, putting everything together:

    singleElemOnly :: (MonadPlus m) => [a] -> m a
    singleElemOnly []  = mzero
    singleElemOnly [x] = return x
    singleElemOnly _   = mzero
           -- a _ catches everything else, no need to write the exact pattern
    

    Or even more simply, since the third case includes the first:

    singleElemOnly :: (MonadPlus m) => [a] -> m a
    singleElemOnly [x] = return x
    singleElemOnly _   = mzero