1

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!

duplode
  • 33,731
  • 7
  • 79
  • 150
Marco
  • 1,430
  • 10
  • 18
  • What are the error messages? Always post the compiler errors you see. – bheklilr Jan 12 '15 at 19:33
  • Sorry, the errors were quite long. I added them and fixed the example. – Marco Jan 12 '15 at 19:42
  • Add a type signature to `test1,...`: probably you want these to be of type `Maybe (Int,[Int])` or something similar. GHC picked `Integer` instead of `Int` for you. Once this type is fixed, you now can see that your function `singleElemOnly` returns only that `Int/Integer` type, while its signature promises that it works at any type `a` (and not only for `Int`). This is the type error that GHC reports. – chi Jan 12 '15 at 19:50
  • Further, it is not clear to me what your last function should do. Can you edit your question explaining what you are trying to achieve? My guess at the moment is something like `f [] = mzero ; f (x:_) = return x` but it's hard to tell. – chi Jan 12 '15 at 19:53
  • @chi Sorry, I updated the question with the latest code and error and a better explanation of what I want to achieve. – Marco Jan 12 '15 at 20:01

2 Answers2

2

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
chi
  • 111,837
  • 3
  • 133
  • 218
  • This is almost perfect! But now I need to somehow get `test1` into it as a parameter and that part still eludes me. `singleElemOnly (fmap snd test2)` does not work, it complains that it "couldn't match type `Maybe` with `[]`"? – Marco Jan 12 '15 at 20:53
  • 1
    `singleElemOnly` takes a list. `fmap snd test` has type `Maybe [Int]`. You can do `fmap (singleElement . snd) test` but this gets you a `Maybe (Maybe [Int])`. You probably want `fmap snd test >>= singleElement`. – user2407038 Jan 12 '15 at 21:20
1

First, hoogle is your friend. You can look up signatures and find that getFirst is just fst, getSecond is snd, and your implementation of getValue can be written as fromMaybe false.

In terms of the error messages, value has the type (Integral i) => Maybe i (or something close, I don't have a compiler right now), which is one possible value that singleElemOnly returns. But the signature (MonadPlus m) => [a] -> m a says that it must return any MonadPlus that the caller wants.

Similarly, if you are running this in GHCi, then the type of test2 defaults to Maybe (Integer, [Integer]), but singleElemOnly must be able to return any Integral type.

crockeea
  • 21,651
  • 10
  • 48
  • 101