Abraham P
Abraham P

Reputation: 15481

testing functions that return a Maybe Monad

Say I have a function:

safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead xs = Just $ head xs

And a test:

describe "Example.safeHead" $ do
  it "returns the head" $ do
    safeHead [1,2,3] `shouldBe` Just 1

  it "returns Nothing for an empty list" $
    safeHead [] `shouldBe` Nothing

This however produces:

No instance for (Eq a0) arising from a use of ‘shouldBe’
The type variable ‘a0’ is ambiguous
Note: there are several potential instances:
  instance (Eq a, Eq b) => Eq (Either a b)
    -- Defined in ‘Data.Either’
  instance forall (k :: BOX) (s :: k). Eq (Data.Proxy.Proxy s)
    -- Defined in ‘Data.Proxy’
  instance (GHC.Arr.Ix i, Eq e) => Eq (GHC.Arr.Array i e)
    -- Defined in ‘GHC.Arr’
  ...plus 88 others
In the second argument of ‘($)’, namely
  ‘safeHead [] `shouldBe` Nothing’
In a stmt of a 'do' block:
  it "returns Nothing for an empty list"
  $ safeHead [] `shouldBe` Nothing
In the second argument of ‘($)’, namely
  ‘do { it "returns the head"
        $ do { safeHead [...] `shouldBe` Just 1 };
        it "returns Nothing for an empty list"
        $ safeHead [] `shouldBe` Nothing }’

Why? And how can I fix it?

Upvotes: 3

Views: 868

Answers (3)

user8145390
user8145390

Reputation:

This code tests a Maybe Monad that returns a "Just" value:

     import Test.Tasty
     import Test.Tasty.HUnit
     import Data.List         

     main = defaultMain tests

     tests = testGroup "Tests uncons form Data.List"

     [testCase "Show uncons [1,2,3,4] = (1, [2,3,4])" $
     Just (1,[2,3,4]) @=? (uncons [1,2,3,4])]

Upvotes: 0

Yuras
Yuras

Reputation: 13876

As user2407038 commented, compiler doesn't know how to instantiate a. The fix he proposed is probably the best one -- you should specify type of a explicitly.

But for completeness I'd like to note, that there is other solution, extended default rules:

{-# LANGUAGE ExtendedDefaultRules #-}

describe "Example.safeHead" $ do
  it "returns the head" $ do
    safeHead [1,2,3] `shouldBe` Just 1

  it "returns Nothing for an empty list" $
    safeHead [] `shouldBe` Nothing

The extension modifies the standard defaulting rules to include more cases, e.g. Eq type class.

Added: After some thinking I reconsider my answer a bit. Results of unit testing of polymorphic functions should not depend of a particular way to instantiate type variables. So probably extended default rules is The Right Thing in tests? I never used them it real code, so I can't say for sure, but it definitely worth thinking about.

Upvotes: 4

Zeta
Zeta

Reputation: 105935

shouldBe                :: (Eq a, Show a) => a -> a -> Expectation
safeHead                ::         [a] ->    Maybe a
[]                      ::         [a]
safeHead []             ::                   Maybe a
Nothing                 ::                   Maybe a
shouldBe (safeHead [])  :: (Eq a, Show a) => Maybe a -> Expectation

shouldBe (safeHead []) Nothing ::                       Expectation -- but what's `a`?

As you can see, a is completely ambiguous. It can a be any type that has Show and Eq instances. This is also part of your error message:

No instance for (Eq a0) arising from a use of ‘shouldBe’
The type variable ‘a0’ is ambiguous

So pick one:

it "returns Nothing for an empty list" $
    safeHead [] `shouldBe` (Nothing :: Maybe ())

While you're at it, use QuickCheck to verify that safeHead works like head:

it "returns Just (head xs) for a non-empty list" $ property $ \(NonEmpty xs) ->
    safeHead xs `shouldBe` (Just (head xs) :: Maybe Integer)

Upvotes: 2

Related Questions