Question

I'm fooling with an example from Learn You a Haskell and I'm not sure what's going wrong. Here's the original example, which mimics truthy/falsey semantics:

class YesNo a where
    yesno :: a -> Bool

A straightforward instance is given by:

instance YesNo Int where
    yesno 0 = False
    yesno _ = True

And then later:

instance YesNo (Maybe a) where
    yesno (Just _) = True
    yesno Nothing = False

This makes a certain amount of sense, but I find the notion that yesno (Just False) == True to be a little counterintuitive, and so I tried to modify it like so:

instance YesNo (Maybe a) where
    yesno (Just b) = yesno b
    yesno Nothing = False

So that in the case where the Maybe instance contains a value we get the truthiness of that value itself. However, this fails with the error No instance for (YesNo a) arising from a use ofyesno'`. What am I doing wrong?

Was it helpful?

Solution

You need to tell the compiler that the type a must have a YesNo instance:

instance YesNo a => YesNo (Maybe a) where
    yesno (Just a) = yesno a
    yesno Nothing  = False

Testing:

> yesno (Just False)
False

OTHER TIPS

You can make an instance of YesNo for Maybe a that only works when a is also an instance of YesNo:

instance (YesNo a) => YesNo (Maybe a) where
    yesno (Just b) = yesno b
    yesno Nothing = False

The (YesNo a) => says, "for every type a, such that a is an instance of YesNo". The entire instance declaration the reads something like, "For every type a, such that a is an instance of YesNo, Maybe a is also an instance of YesNo where ...

So that in the case where the Maybe instance contains a value we get...

That can't be done really well in Haskell. Type class instances shouldn't be read "I now transfer this data type from the set of non-instances to the set of instances", but rather "I now describe how to use this type as a instance of that class". There's not really a notion of not being an instance of a class, only of not finding an instance.

So if you want to decide based on the value contained in the Maybe, you probably should do as suggested by Cirdec and Mikhail Glushenkov: instance YesNo a => YesNo (Maybe a). Of course that means e.g. Maybe () will not be a YesNo instance. You either get the same behaviour for all types, or another, but again all-the-same behaviour for a particular set of types. But you can't get different behaviour depending on if a type is in the class.

There is actually one way to achieve that, but it's kind of frowned upon:

{-# LANGUAGE OverlappingInstances #-}

instance YesNo (Maybe a) where
  yesno (Just _) = True
  yesno Nothing = False

newtype YesNo_ a = YesNo_ a

instance YesNo a => YesNo (Maybe (YesNo_ a)) where
  yesno (Just (YesNo_ a)) = yesno a
  yesno Nothing  = False

Not only is this cumbersome, it also requires the somewhat unsafe OverlappingInstances extension.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top