You can with this library: https://github.com/mikeizbicki/ifcxt. Being able to call show
on a value that may or may not have a Show
instance is one of the first examples it gives. This is how you could adapt that for V a
:
{-
{-
{-
{-
{-
{-
{-
{-
import IfCxt
import Data.Typeable
mkIfCxtInstances ''Show
data V a = V a
instance forall a. IfCxt (Show a) => Show (V a) where
show (V a) = ifCxt (Proxy::Proxy (Show a))
(show a)
"<<unshowable>>"
This is the essence of this library:
class IfCxt cxt where
ifCxt :: proxy cxt -> (cxt => a) -> a -> a
instance {-# OVERLAPPABLE #-} IfCxt cxt where ifCxt _ t f = f
I don't fully understand it, but this is how I think it works:
It doesn't violate the "open world" assumption any more than
instance {-
show _ = "<<unshowable>>"
does. The approach is actually pretty similar to that: adding a default case to fall back on for all types that do not have an instance in scope. However, it adds some indirection to not make a mess of the existing instances (and to allow different functions to specify different defaults). IfCxt
works as a a "meta-class", a class on constraints, that indicates whether those instances exist, with a default case that indicates "false.":
instance {-
It uses TemplateHaskell to generate a long list of instances for that class:
instance {-# OVERLAPS #-} IfCxt (Show Int) where ifCxt _ t f = t
instance {-# OVERLAPS #-} IfCxt (Show Char) where ifCxt _ t f = t
which also implies that any instances that were not in scope when mkIfCxtInstances
was called will be considered non-existing.
The proxy cxt
argument is used to pass a Constraint
to the function, the (cxt => a)
argument (I had no idea RankNTypes allowed that) is an argument that can use the constraint cxt
, but as long as that argument is unused, the constraint doesn't need to be solved. This is similar to:
f :: (Show (a -> a) => a) -> a -> a
f _ x = x
The proxy
argument supplies the constraint, then the IfCxt
constraint is solved to either the t
or f
argument, if it's t
then there is some IfCxt
instance where this constraint is supplied which means it can be solved directly, if it's f
then the constraint is never demanded so it gets dropped.
This solution is imperfect (as new modules can define new Show
instances which won't work unless it also calls mkIfCxtInstances
), but being able to do that would violate the open world assumption.
instanceof
operator like Java or similar languages provide. (There are more advanced techniques that can be used in some cases to similar runtime type reflection, but if you're a beginner you should stick to the basics first!) – Luis CasillasShow
instance for some type you try toshow
, there will be an error; but this is understood to be conceptually “you forgot to write the necessary instance” instead of “you tryed to show a type which is not showable“. Type classes are open, anybody can later define instances for some library class. The compiler can't possibly prove that this won't happen, when it compiles the library! – leftaroundaboutinstance Show a => Show (V a) where ...
. Then the "Some Error" becomes a compile time type error. – user2407038