thor
thor

Reputation: 22520

difference between isNothing and (== Nothing) in Haskell?

I'm confused about why the two functions below involving Nothing are different:

coalesce  m1 m2 = if  isNothing m1 then m2 else m1

coalesce' m1 m2 = if  (m1 == Nothing) then m2 else m1

The first one has type:

λ> :t coalesce
coalesce :: Maybe a -> Maybe a -> Maybe a

as expected. But the second one has:

λ> :t coalesce'
coalesce' :: Eq a => Maybe a -> Maybe a -> Maybe a

Why does using (==Nothing) introduce the Eq a constraint?

(GHC 8.2.2)

Upvotes: 16

Views: 11945

Answers (3)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477190

Let us first aim to design a (==) function over Maybe values. Usually we consider two things the same if they have the same data constructor, and the parameters are all equal. So we consider F x1 x2 x3 and G y1 y2 y3 the same if F and G are the same data constructor, and x1 == y1, x2 == y2, and x3 == y3.

So if we implement this for Maybe, there are two cases that are equal: two Nothings, and two Justs, where the values the Justs encapsulate are the same, so:

instance Eq a => Eq (Maybe a) where
    (==) Nothing Nothing = True
    (==) (Just x) (Just y) = x == y
    (==) _ _ = False

is the most logical way to implement the Eq instance for Maybe a. In the implementation, we use x == y, hence we added the Eq a type constraint.

Now Haskell sees functions conceptually as blackboxes. For Maybe, it thus sees (==) as (==) :: Eq a => Maybe a -> Maybe a -> Bool. If you thus use this (==) function, it will always require the type constraint Eq a, regardless whether the specific usage will ever need this type constraint to perform the x == y check. If you write (== Nothing), then we see, that the second clause ((==) (Just x) (Just y)) will never be used, but since Haskell treats functions as blackboxes, it does not known in what cases the type constraint is relevant.

isNothing :: Maybe a -> Bool only checks if the value is a Nothing, if it is a Just, then it is always False, regardless what the value is the Just constructor wraps, it is thus implemented like:

isNothing :: Maybe a -> Bool
isNothing Nothing = True
isNothing _ = False

So we do not need the Eq a type constraint here: we do not check equality of elements Just constructor(s) wrap. So again if we use this, Haskell only checks the type signature, notices that there is no Eq a type constraint involved and hence does not add it to functions that use this function.

Notice that what you implement here actually already is implemented in the Control.Applicative module: (<|>) :: Alternative f => f a -> f a -> f a, for example:

Prelude> import Control.Applicative
Prelude Control.Applicative> (<|>) Nothing Nothing
Nothing
Prelude Control.Applicative> (<|>) Nothing (Just 3)
Just 3
Prelude Control.Applicative> (<|>) (Just 3) Nothing
Just 3
Prelude Control.Applicative> (<|>) (Just 3) (Just 2)
Just 3

Upvotes: 4

Ry-
Ry-

Reputation: 225064

== is a function Eq a => a -> a -> Bool. You’re making one of its operands Nothing, so a is a Maybe b for some type b, but that Eq a restriction still applies – the Maybe b has to have an Eq instance. Maybe b includes a definition for such an instance – Eq (Maybe b) – for all Eq b.

In other words, == is just a function and it doesn’t know ahead of time that you’re providing Nothing as an argument. It only knows it’s some Maybe a, and it needs to be able to compare equality if it happens to be a Just ….

In other other words, here’s how you can define the == that already exists for Maybes:

equals a b = case a of
    Nothing -> isNothing b
    Just x -> case b of
        Nothing -> False
        Just y -> x == y

Notice how x == y appears, meaning there must be an Eq instance for the type of x and y.

Upvotes: 20

melpomene
melpomene

Reputation: 85827

Because the type of == is

(==) :: (Eq a) => a -> a -> Bool

Your second definition (coalesce') uses ==, so it inherits the Eq constraint on its arguments from ==.


Strictly speaking coalesce' uses == on values of type Maybe a, but there's an instance

instance (Eq a) => Eq (Maybe a) where ...

so the Eq (Maybe a) constraint becomes Eq a, because that's what's needed to support == on Maybe a.

Upvotes: 6

Related Questions