0
votes

I have a typeclass:

class Wrapper w where
    open :: w -> Map String Int
    close :: Map String Int -> w

It doesn't look very useful, but I use it to strongly (not just a type synonym) distinguish between semantically different varieties of Map String Ints:

newtype FlapMap = Flap (Map String Int)
newtype SnapMap = Snap (Map String Int)
...

and still have functions that operate on any type of the class.

  1. Is there a better way to do this distinction (maybe without the Wrapper instances boilerplate)?

I want to do this:

instance (Wrapper wrapper) => Show wrapper where
    show w = show $ toList $ open w

instead of writing many boilerplate Show instances as well.

Via FlexibleInstances and UndecidableInstances, GHC leads me to a point where it thinks my instance declaration applies to everything, as it allegedly clashes with the other Show instances in my code and in GHC.Show. HaskellWiki and StackOverflow answerers and HaskellWiki convince me OverlappingInstances is not quite safe and possibly confusing. GHC doesn't even suggest it.

  1. Why does GHC first complain about not knowing which instance of fx Show Int to pick (so why it doesn't look at the constraint I give at compile time?) and then, being told that instances may overlap, suddenly know what to do?

  2. Can I avoid allowing OverlappingInstances with my newtypes?

1
Is deriving Show different to what you want to achieve?chi
Yes. I don't just want FlapMap (fromList [...]).Leif Willerts
I would not override the Show instance for it is really helpful for creating output, showing it in ghci and copy it to a test case - especially in combination with a prettyprinting library. I would rather create a UserFriendlyShow typeclass - but then you need the OverlappingInstances anyway.epsilonhalbe

1 Answers

5
votes

You can’t do this without OverlappingInstances, which as you mention, is unpredictable. It won’t help you here, anyway, so you pretty much can’t do this at all without a wrapper type.

That’s rather unsatisfying, of course, so why is this the case? As you’ve already determined, GHC does not look at the instance context when picking an instance, only the instance head. Why? Well, consider the following code:

class Foo a where
  fooToString :: a -> String

class Bar a where
  barToString :: a -> String

data Something = Something

instance Foo Something where
  fooToString _ = "foo something"

instance Bar Something where
  barToString _ = "bar something"

instance Foo a => Show a where
  show = fooToString

instance Bar a => Show a where
  show = barToString

If you consider the Foo or Bar typeclasses in isolation, the above definitions make sense. Anything that implements the Foo typeclass should get a Show instance “for free”. Unfortunately, the same is true of the Bar instance, so now you have two valid instances for show Something.

Since typeclasses are always open (and indeed, Show must be open if you are able to define your own instances for it), it’s impossible to know that someone will not come along and add their own similar instance, then create an instance on your datatype, creating ambiguity. This is effectively the classic diamond problem from OO multiple inheritance in typeclass form.

The best you can get is to create a wrapper type that provides the relevant instances:

{-# LANGUAGE ExistentialQuantification #-}

data ShowableWrapper = forall w. Wrapper w => ShowableWrapper w

instance Show ShowableWrapper where
  show (ShowableWrapper w) = show . toList $ open w

At that point, though, you really aren’t getting much of an advantage over just writing your own showWrapper :: Wrapper w => w -> String function.