0

I'm writing code to implement extension by definitions in mathematical logic.

It takes in a description of languages and their extensions, and it outputs a new haskell file which will parse a high-level language into a lower-level one. Of course, if I can turn language C into language B, and language B into language A, then by composing I can turn C to A.... and yet...

Here is a minimal example of the problem I'm facing:

data A = EmptyA | NodeA A A
data B = EmptyB | NodeB B B | UnaryB B
data C = EmptyC | NodeC C C | UnaryC C | TernaryC C C C


class ToA a where
  convertToA :: a -> A

class ToB a where
  convertToB :: a -> B


instance ToA B where
  convertToA EmptyB      = EmptyA
  convertToA (NodeB l r) = NodeA (convertToA l) (convertToA r)
  convertToA (UnaryB l)  = NodeA (convertToA l) EmptyA

instance ToB C where
  convertToB EmptyC           = EmptyB
  convertToB (NodeC l r)      = NodeB (convertToB l) (convertToB r)
  convertToB (UnaryC l)       = UnaryB (convertToB l)
  convertToB (TernaryC l m r) = NodeB (convertToB l) (NodeB (convertToB m) (convertToB r))


-- instance (ToB a) => ToA a where
--   convertToA = convertToA . convertToB

-- I shouldn't have to write this
instance ToA C where
  convertToA  = convertToA . convertToB

Intuitively, there's nothing wrong with instance (ToB a) => ToA a, but the compiler doesn't like it. The code as is, compiles properly, but upon replacing the explicit ToA C instance with the commented version, I receive the following error:


minimal.hs:25:21: error:
    • Illegal instance declaration for ‘ToA a’
        (All instance types must be of the form (T a1 ... an)
         where a1 ... an are *distinct type variables*,
         and each type variable appears at most once in the instance head.
         Use FlexibleInstances if you want to disable this.)
    • In the instance declaration for ‘ToA a’
   |
25 | instance (ToB a) => ToA a where
   |                     ^^^^^

Of course, I'm not afraid of language extensions, so I do as I'm told and add FlexibleInstances, even though I don't think it should help here. After doing this, I'm told to try UndecidableInstances... And that's where the trail stops. I'm still getting a type error, but I'm not sure what to do about it.

minimal.hs:29:16: error:
    • Overlapping instances for ToA B
        arising from a use of ‘convertToA’
      Matching instances:
        instance ToB a => ToA a -- Defined at minimal.hs:28:10
        instance ToA B -- Defined at minimal.hs:16:10
    • In the first argument of ‘(.)’, namely ‘convertToA’
      In the expression: convertToA . convertToB
      In an equation for ‘convertToA’:
          convertToA = convertToA . convertToB
   |
29 |   convertToA = convertToA . convertToB
   |                ^^^^^^^^^^

This error message is especially confusing to me, since I only have one definition for ToA B. This error would make more sense if B was itself an instance of ToB, say by setting convertToB = id. Of course, that's not the case here...

How am I supposed to properly handle this issue? Thanks in advance! ^_^

  • 1
    This is a fairly well-known subtlety with GHC’s implementation of typeclasses. When searching for a typeclass implementation, GHC only looks at the stuff on the right of the `=>` (called the ‘head’), and ignores the ‘context’ to the left of `=>`; it only checks the context once it has matched a head. This means that an instance `(ToB a) => ToA a` will overlap with _any_ other `ToA` instance, so you can’t define it. But unfortunately I don’t have a solution (which is why I posted this as a comment and not an answer). – bradrn Apr 27 '20 at 00:33
  • Thank you! Between your comment and some help from the #haskell irc, I at least understand the problem now. As for debugging... I'll post an answer if I get it before anyone else figures it out – HallaSurvivor Apr 27 '20 at 01:45

1 Answers1

3

You're doing the right things. And you're right to be cautious about Overlapping instances warnings. In this case it is coherent. And you're not afraid of language extensions, so you want:

instance {-# OVERLAPPABLE #-} (ToB a) => ToA a where
  convertToA = convertToA . convertToB

That thing in {-# #-} is a pragma, which is a closely-tailored way to invoke a language extension, for just this instance. OVERLAPPABLE means allow there to be a more specific instance (ToA B), and choose that by preference.

Your constraint (ToB a) => is indeed Undecidable because it is no smaller than the instance head. UndecidableInstances is a relatively 'safe' extension compared to overlaps.

With overlaps the unsafe uses are INCOHERENT (just as bad as it sounds) or 'Orphan instances' -- which give you a compiler warning sometimes; doesn't apply here because all your instances are in the same module as the class declaration.

AntC
  • 2,623
  • 1
  • 13
  • 20