Search code examples
haskellhspec

HSpec Nothing expectation failing to compile


I'm learning Haskell and I've written this function:

safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:xs) = Just x

I'm now trying to test it with HSpec:

import Test.Hspec

main :: IO ()
main = hspec spec

spec :: Spec
spec =

  describe "safeHead" $
    it "should return Nothing for empty list" $
      safeHead [] `shouldBe` Nothing

But that fails to compile:

Error:(14, 19) ghc: 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 (Maybe a) -- Defined in ‘GHC.Base’
      instance Eq a => Eq (GHC.Real.Ratio a) -- Defined in ‘GHC.Real’
      instance Eq Ordering -- Defined in ‘ghc-prim-0.4.0.0:GHC.Classes’
      ...plus 31 others
    In the second argument of ‘($)’, namely
      ‘safeHead [] `shouldBe` Nothing’
    In the second argument of ‘($)’, namely
      ‘it "should return Nothing for empty list"
       $ safeHead [] `shouldBe` Nothing’
    In the expression:
      describe "safeHead"
      $ it "should return Nothing for empty list"
        $ safeHead [] `shouldBe` Nothing

I have also tried this:

safeHead :: (Eq a) => [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:xs) = Just x

But that still fails with:

Error:(14, 19) ghc: 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 Eq Data.Monoid.All -- Defined in ‘Data.Monoid’
      instance forall (k :: BOX) (f :: k -> *) (a :: k).
               Eq (f a) =>
               Eq (Data.Monoid.Alt f a)
        -- Defined in ‘Data.Monoid’
      ...plus 43 others
    In the second argument of ‘($)’, namely
      ‘safeHead [] `shouldBe` Nothing’
    In the second argument of ‘($)’, namely
      ‘it "should return Nothing for empty list"
       $ safeHead [] `shouldBe` Nothing’
    In the expression:
      describe "safeHead"
      $ it "should return Nothing for empty list"
        $ safeHead [] `shouldBe` Nothing

I don't know what's the problem here. If I try other tests like these, it compiles fine:

    it "should return the head" $ do
      safeHead [1] `shouldBe` Just 1
      safeHead [2,3,4,5,6,1] `shouldBe` Just 2

So, it's something about Nothing itself, that it cannot be compared by equals? How do you assert that something returns Nothing then? Or is my function too generic?

SIDE NOTE: I've seen a similar error with this function:

palindrome :: (Eq a) => [a] -> [a]
palindrome xs = xs ++ reverse xs

When trying to test for empty lists:

palindrome [] `shouldBe` []

Which fails with:

Error:(26, 21) ghc: 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 (Maybe a) -- Defined in ‘GHC.Base’
      instance Eq a => Eq (GHC.Real.Ratio a) -- Defined in ‘GHC.Real’
      instance Eq Ordering -- Defined in ‘ghc-prim-0.4.0.0:GHC.Classes’
      ...plus 32 others
    In a stmt of a 'do' block: palindrome [] `shouldBe` []
    In the second argument of ‘($)’, namely
      ‘do { palindrome [] `shouldBe` [] }’
    In the second argument of ‘($)’, namely
      ‘it
         "should turn a list into a palindrome, so it reads same both forwards and backwards"
       $ do { palindrome [] `shouldBe` [] }’

Solution

  • So, it's something about Nothing itself, that it cannot be compared by equals?

    What's Nothing's type? It's Nothing :: Maybe a. And GHC doesn't like a in this context: "The type variable ‘a0’ is ambiguous". After all, shouldBe takes anything that can be compared with (==) and shown. And Maybe a is an instance of Eq if a is an instance of Eq. GHC can't possibly know which a you want to use, so you need to specify it by hand:

      describe "safeHead" $
        it "should return Nothing for empty list" $
          safeHead [] `shouldBe` (Nothing :: Maybe Int)
    

    That's not coercion, you're just making clear which of all possible types you want to use. Other examples:

      describe "safeHead" $
        it "should return Nothing for empty list" $ do
          safeHead [] `shouldBe` (Nothing :: Maybe Int)
          safeHead [] `shouldBe` (Nothing :: Maybe ())
          safeHead [] `shouldBe` (Nothing :: Maybe Integer)
          safeHead [] `shouldBe` (Nothing :: Maybe Char)