3

For some reason, GHC seems to be deciding that my datatype (with two type parameters) instantiates Bifunctor for no reason at all.

The funniest thing is that this is only used to tell me that there are overlapping instances of Functor for this datatype because I gave an instantiation of Functor for any Bifunctor (and a particular one for the datatype). But, if I try to do bimap on it, it tells me that there is no instance of Bifunctor.

{-# LANGUAGE FlexibleInstances #-}
module BifunctorError where

import Data.Bifunctor

instance Bifunctor f => Functor (f t) where
    fmap = bimap id

data Provenance p t = Provenance p t

-- Uncomment and try to compile: Overlapping instances ???
--instance Functor (Provenance p) where
--  fmap f (Provenance p x) = Provenance p (f x)

I would expect that, because I have not provided anything to indicate that Provenance instantiates Bifunctor, it would not, and so the instance of Functor derived from Bifunctor should be irrelevant. Is this a bug with FlexibleInstances?

  • It is. Thank you very much, I had looked for a while but found anything. It took me a while to understand what the problem is. Can I just ask, if you know, why does Haskell not check the constraints to decide potential instances? Is it because the constraints are rather passed back to the resulting type of the expression? Something like: "An absence of an instance Bifunctor Provenance is not a proof that Provenance would never be an instance of Bifunctor, and so there could be overlapping instances"? Because type checking is intuitionistic logic and etc.? – Juan Casanova Jaquete May 14 '19 at 01:46
  • (1) That is pretty much it: absence of proof is not proof of absence. In particular, someone else using your code might give `Provenance` a `Bifunctor` instance without you ever knowing about it. Haskell classes work under [the "open world assumption"](https://stackoverflow.com/q/8728596/2751851). (2) See also [this very nice answer](https://stackoverflow.com/a/3216937/2751851), which I didn't add to the list of targets because there is too much going on elsewhere in that Q&A. – duplode May 14 '19 at 02:04

1 Answers1

2

When looking for a matching typeclass instance, GHC only looks at the head, not the conditions. It matches the conditions as a secondary step. So when GHC is looking for a Functor instance, all it sees is

instance (Don't care) => Functor (f t) where
instance (Don't care) => Functor (Provenance p) where

And both of those instances match just fine. The (Don't care) bit doesn't even come into play until later. So you have some overlapping instances, and that's a problem.

You can see a bit more about this in this question. The "solution" is to toy with the GHC extension OverlappingInstances, which is a bit of a rabbit hole in and of itself.

Silvio Mayolo
  • 24,199
  • 3
  • 34
  • 65