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?