The premise behind Haskell's typeclass system is grouping types that all share a common property. So if you know a type instantiates some class C
, you know certain things about that type. For example, Int instantiates Eq
, so we know that elements of Int can be compared for equality.
Suppose we have a group of values, and we don't know if they are all the same type, but we do know they all instantiate some class, i.e. we know all the values have a certain property. It might be useful to throw all these values into a list. We can't do this normally because lists are homogeneous with respect to types: they can only contain a single type. However, existential types allow us to loosen this requirement by defining a 'type hider' or 'type box':
Example: Constructing a heterogeneous list
data ShowBox = forall s. Show s => SB s
heteroList :: [ShowBox]
heteroList = [SB (), SB 5, SB True]
We won't explain precisely what we mean by that datatype definition, but its meaning should be clear to your intuition. The important thing is that we're calling the constructor on three values of different types, and we place them all into a list, so we must end up with the same type for each one. Essentially this is because our use of the forall
keyword gives our constructor the type SB :: forall s. Show s => s -> ShowBox
. If we were now writing a function that we were intending to pass heteroList
, we couldn't apply any functions like not to the values inside the SB
because they might not be Bool
s. But we do know something about each of the elements: they can be converted to a string via show. In fact, that's pretty much the only thing we know about them.
Example: Using our heterogeneous list
instance Show ShowBox where
show (SB s) = show s -- (*) see the comment in the text below
f :: [ShowBox] -> IO ()
f xs = mapM_ print xs
main = f heteroList
Let's expand on this a bit more. In the definition of show for ShowBox
– the line marked with (*) see the comment in the text below – we don't know the type of s
. But as we mentioned, we do know that the type is an instance of Show
due to the constraint on the SB
constructor. Therefore, it's legal to use the function show on s
, as seen in the right-hand side of the function definition.
As for f
, recall the type of print
:
Example: Types of the functions involved
print :: Show s => s -> IO () -- print x = putStrLn (show x)
mapM_ :: (a -> m b) -> [a] -> m ()
mapM_ print :: Show s => [s] -> IO ()
As we just declared ShowBox
an instance of Show
, we can print the values in the list.
Applying it to your situation, you get
{-# LANGUAGE ExistentialQuantification #-}
data ShowBox = forall a. Show a => SB a
instance Show ShowBox where
show (SB s) = show s
data Code = Code_A | Code_B | Code_C
deriving (Eq,Show)
l :: [ShowBox]
l = [SB Code_A, SB 3, SB Code_C, SB 0, SB "but..."]
f = mapM_ print l
Output:
ghci> f
Code_A
3
Code_C
0
"but..."
The other answers show the approach you likely want, but this is here for completeness and to offer food for thought. As the SB "but..."
bit suggests, ShowBox
is more permissive.
Give us more information about the problem you're working on, and we can give you more helpful suggestions that are specific to your situation.