I have the following definition:
{-# LANGUAGE ExistentialQuantification #-}
module Test
where
class Named a where
name :: a -> String
data Wrap = forall a . (Named a, Read a) => Wrap (a -> IO ())
I want to write Named
instance for Wrap
. The next doesn't work:
instance Named Wrap where
name (Wrap named) =
let a = undefined
_ = named a
in name a
The error:
Could not deduce (Named a0) arising from a use of ‘name’
from the context (Named a, Read a)
But the following works:
instance Named Wrap where
name (Wrap named) =
let a = read undefined
_ = named a
in name a
The only difference I see is that a
is in covariant position in read
, but in contravariant possition in name
. Why doesn't the first instance declaration work?
The first instance doesn't work because it doesn't trigger the monomorphism restriction, so a
gets a polymorphic type which is instantiated differently in named a
and name a
. On the other hand, when writing a = read undefined
, we find that a
has a typeclass-restricted type, so the monomorphism restriction kicks in and we must choose a specific type for a
; since named a
uniquely identifies this type, that type is chosen and it is not instantiated at a different type in name a
.
You can cause the read
version to fail by turning on NoMonomorphismRestriction
to verify that this is the correct explanation.
You can fix the problem by using a lambda instead of a let, as in:
instance Named Wrap where
name (Wrap named) = (\a -> (\_ -> name a) (named a)) undefined
(Generally, let x = e in e'
is the same as (\x -> e') e
, provided x
is monomorphic and not recursive, and I have performed this rewrite twice here.)
However, I would suggest a more serious reworking of your approach to avoid undefined
entirely, if possible. The standard trick for this is:
class Named a where
name :: proxy a -> String
proxyForFun :: (a -> IO ()) -> Proxy a
proxyForFun _ = Proxy
instance Named Wrap where
name (Wrap named) = name (proxyForFun named)
However, the type of name
is quite restrictive: it is no longer possible to write instances in which name
inspects its argument, so if that was a feature you needed this approach will not work.