Learn You a Haskell for Great Good! - Miran Lipovaca [64]
(==) :: Maybe -> Maybe -> Bool
But this does:
(==) :: (Eq m) => Maybe m -> Maybe m -> Bool
This is just something to think about, because == will always have a type of (==) :: (Eq a) => a -> a -> Bool, no matter what instances we make.
Oh, and one more thing: If you want to see what the instances of a type class are, just type :info YourTypeClass in GHCi. For instance, typing :info Num will show which functions the type class defines, and it will give you a list of the types in the type class. :info works for types and type constructors, too. If you do :info Maybe, it will show you all the type classes that Maybe is an instance of. Here’s an example:
ghci> :info Maybe
data Maybe a = Nothing | Just a -- Defined in Data.Maybe
instance (Eq a) => Eq (Maybe a) -- Defined in Data.Maybe
instance Monad Maybe -- Defined in Data.Maybe
instance Functor Maybe -- Defined in Data.Maybe
instance (Ord a) => Ord (Maybe a) -- Defined in Data.Maybe
instance (Read a) => Read (Maybe a) -- Defined in GHC.Read
instance (Show a) => Show (Maybe a) -- Defined in GHC.Show
A Yes-No Type Class
In JavaScript and some other weakly typed languages, you can put almost anything inside an if expression. For example, in JavaScript, you can do something like this:
if (0) alert("YEAH!") else alert("NO!")
Or like this:
if ("") alert ("YEAH!") else alert("NO!")
Or like this:
if (false) alert("YEAH!") else alert("NO!")
All of these will throw an alert of NO!.
However, the following code will give an alert of YEAH!, since JavaScript considers any nonempty string to be a true value:
if ("WHAT") alert ("YEAH!") else alert("NO!")
Even though strictly using Bool for Boolean semantics works better in Haskell, let’s try to implement this JavaScript-like behavior, just for fun! We’ll start out with a class declaration:
class YesNo a where
yesno :: a -> Bool
This is pretty simple. The YesNo type class defines one function. That function takes one value of a type that can be considered to hold some concept of trueness and tells us for sure if it’s true or not. Notice that from the way we use a in the function that a must be a concrete type.
Next up, let’s define some instances. For numbers, we’ll assume that (as in JavaScript) any number that isn’t 0 is true in a Boolean context and 0 is false.
instance YesNo Int where
yesno 0 = False
yesno _ = True
Empty lists (and by extension, strings) are a no-ish value, while nonempty lists are a yes-ish value.
instance YesNo [a] where
yesno [] = False
yesno _ = True
Notice how we just put a type parameter a in there to make the list a concrete type, even though we don’t make any assumptions about the type that’s contained in the list.
Bool itself also holds trueness and falseness, and it’s pretty obvious which is which:
instance YesNo Bool where
yesno = id
But what’s id? It’s just a standard library function that takes a parameter and returns the same thing, which is what we would be writing here anyway.
Let’s make Maybe a an instance, too:
instance YesNo (Maybe a) where
yesno (Just _) = True
yesno Nothing = False
We didn’t need a class constraint, because we made no assumptions about the contents of the Maybe. We just said that it’s true-ish if it’s a Just value and false-ish if it’s a Nothing. We still need to write out (Maybe a) instead of just Maybe. If you think about it, a Maybe -> Bool function can’t exist (because Maybe isn’t a concrete type), whereas a Maybe a -> Bool is fine and dandy. Still, this is really cool, because now any type of the form Maybe something is part of YesNo, and it doesn’t matter what that something is.
Previously, we defined a Tree a type that represented a binary search tree. We can say an empty tree is false-ish, and anything that’s not an empty tree is true-ish:
instance YesNo (Tree a) where
yesno EmptyTree = False
yesno _ = True
Can a traffic light be a yes or no value? Sure. If it’s red, you stop. If it’s green, you go. (If it’s yellow? Eh, I usually run the