I think I may have asked this on Haskell-Cafe at some point, but damned if I can find the answer now... So I'm asking it again here, so hopefully in future I can find the answer!
Haskell is fantastic at dealing with parametric polymorphism. But the trouble is that not everything is parametric. As a trivial example, suppose we want to fetch the first element of data out of a container. For a parametric type, that's trivial:
class HasFirst c where first :: c x -> Maybe x instance HasFirst [] where first [] = Nothing first (x:_) = Just x
Now try and write an instance for ByteString. You can't. Its type doesn't mention the element type. You also cannot write an instance for Set, because it requires an Ord constraint - but the class head doesn't mention the element type, so you cannot constrain it.
Associated types provide a neat way to completely fix these problems:
class HasFirst c where type Element c :: * first :: c -> Maybe (Element c) instance HasFirst [x] where type Element [x] = x first [] = Nothing first (x:_) = Just x instance HasFirst ByteString where type Element ByteString = Word8 first b = b ! 0 instance Ord x => HasFirst (Set x) where type Element (Set x) = x first s = findMin s
We now have a new problem, however. Consider trying to "fix" Functor so that it works for all container types:
class Functor f where type Element f :: * fmap :: (Functor f2) => (Element f -> Element f2) -> f -> f2
This doesn't work at all. It says that if we have a function from the element type of f to the element type of f2, then we can turn an f into an f2. So far so good. However, there is apparently no way to demand that f and f2 are the same sort of container!
Under the existing Functor definition, we have
fmap :: (x -> y) -> [x] -> [y] fmap :: (x -> y) -> Seq x -> Seq y fmap :: (x -> y) -> IO x -> IO y
But we do not have fmap :: (x -> y) -> IO x -> [y]. That is quite impossible. But the class definition above allows it.
Does anybody know how to explain to the type system what I actually meant?
Edit
The above works by defining a way to compute an element type from a container type. What happens if you try to do it the other way around? Define a function to compute a container type from an element type? Does that work out any easier?