3

Say I have two classes like this:

{-# LANGUAGE MultiParamTypeClasses, AllowAmbiguousTypes, FlexibleContexts #-}
class Foo a b

class Bar a where
  foo :: Foo a b => a

Notably, the b of foo can neither be inferred from the use of foo nor from the instance head. Now let's try to implement an instance for this:

data A = A

-- This implementation doesn't actually need the Foo A b constraint, but for the sake of the question it's required here.
a :: Foo A b => A
a = A

So far so good. a is supposed to become the foo in our instance. It even has the right type, right? So let's go ahead and implement the instance:

instance Bar A where
  foo = a

Unfortunately this instance doesn't compile and gives this error instead:

instance-ambiguous.hs:15:9: error: …
    • Could not deduce (Foo A b0) arising from a use of ‘a’
      from the context: Foo A b
        bound by the type signature for:
                   foo :: forall b. Foo A b => A
        at /home/sven/instance-ambiguous.hs:15:3-5
      The type variable ‘b0’ is ambiguous
    • In the expression: a
      In an equation for ‘foo’: foo = a
      In the instance declaration for ‘Bar A’
   |
Compilation failed.

At first this error message seems quite nonsensical, because it looks like GHC could just unify b and b0 and infer a perfectly fine type. Then I remembered that b and b0 aren't visible from the type of foo or a and GHC can't unify them, because it has nothing that guarantees that b and b0 are really always exactly the same and this is not such an unexpected error when working with ambiguous types.

Normally when I encounter such errors, I'm able to solve them using TypeApplications and ScopedTypeVariables like this:

{-# LANGUAGE MultiParamTypeClasses, AllowAmbiguousTypes, FlexibleContexts, TypeApplications, ScopedTypeVariables #-}

class Foo a b

class Bar b

foo :: forall b a. (Bar b, Foo a b) => a
foo = undefined

bar :: forall b a. (Bar b, Foo a b) => a
bar = foo @b

Here I can explicitly supply the @b, because the type signature of bar brought it in scope. So I tried to do the same with my instance (using InstanceSigs):

instance Bar A where
  foo :: forall b. Foo A b => A
  foo = a @b

This doesn't compile either and gives this error:

instance-ambiguous.hs:16:10-31: error: …
    • Could not deduce (Foo A b0)
      from the context: Foo A b
        bound by the type signature for:
                   foo :: forall b. Foo A b => A
        at /home/sven/instance-ambiguous.hs:16:10-31
      The type variable ‘b0’ is ambiguous
    • When checking that instance signature for ‘foo’
        is more general than its signature in the class
        Instance sig: forall b. Foo A b => A
           Class sig: forall b. Foo A b => A
      In the instance declaration for ‘Bar A’
   |
Compilation failed.

I'm not sure, but I think this means that GHC thinks that my Foo A b => A in the instance refers to some other b than the one in the class declaration.

Using a pattern declaration on foo to get the original b in scope also doesn't work, because Pattern bindings are forbidden in instance declarations.

Now the question: What are my options to work around this problem?

I know that I can just use Proxy(s/y/ie/)s everywhere and hear none of those ambiguity problems, but I usually find TypeApplications more elegant than Proxys and would like to use them here, especially because the affected classes are part of my public API.

I could also include the b as a class variable of Bar, but I think that would change the meaning of Bar to something that I don't want, because then instances can choose which bs to implement instances for, but I want every Bar a => a to work for every b for which an Foo a b exists.

Kritzefitz
  • 2,644
  • 1
  • 20
  • 35
  • Since you defined `foo` in the `Bar` class, it has an *extra* "implicit" constraint: it should be `(Foo a b, Bar a)`, since the `class Bar a` implies this constraint on `a`. – Willem Van Onsem Jan 31 '19 at 22:15
  • @WillemVanOnsem Ah, yes. I edited those in. Turns out I also forgot to include the `a`s in the `forall`. – Kritzefitz Jan 31 '19 at 22:20
  • 1
    Sigh. This seems to be another limitation of GHC ambiguous types. ([another](https://stackoverflow.com/questions/50159349/type-abstraction-in-ghc-haskell/50160423#50160423)) I find such cases particularly annoying, since they would be trivial to handle in a dependently typed language (Coq/Agda/Idris) as well as, I guess, in GHC Core, since all these languages have explicit type arguments. I hope GHC will improve in this aspect, so that we can finally throw all the proxies away without exceptions. – chi Jan 31 '19 at 23:35
  • 1
    [Relevant GHC proposal](https://github.com/ghc-proposals/ghc-proposals/pull/155). With this, I think you'd just write `instance Bar A where foo @b = _`. Go over there and urge the proposal process on, if you'd like. The more people wanting it, the faster it should get done. – HTNW Feb 01 '19 at 00:16
  • This really seems like an infelicity in InstanceSignatures rather than anything fundamental to me, to the point that I'd be tempted to file it as a bug on GHC's Trac and see if I could convince HQ that `forall b. Foo a b => A` should really be considered exactly as general as `forall b. Foo a b => A`. – Daniel Wagner Feb 01 '19 at 21:26

1 Answers1

3

There doesn't seem to be a way to resolve the ambiguity for instances, so a Proxy or Tagged seems inevitable to define the class, but you can wrap it to use the class with TypeApplications.

class Bar a where
  foo_ :: Foo a b => proxy b -> a

foo :: forall b a. (Bar a, Foo a b) => a
foo = foo_ (Proxy @b)
Li-yao Xia
  • 31,896
  • 2
  • 33
  • 56
  • Yes, that is the solution that I am currently using. I hoped for a solution that doesn't need proxies in the instance, because implementing new instances will be a more prominent part of my API than using them. – Kritzefitz Jan 31 '19 at 23:04
  • Have you tried functional dependencies or is that not applicable either? – Li-yao Xia Jan 31 '19 at 23:10
  • A general solution seems quite difficult, if not impossible, but there could be another special feature of your particular problem that would make things much easier. – Li-yao Xia Jan 31 '19 at 23:11
  • 1
    FunctionalDependencies do solve the problem and the previous iteration of my API actually used TypeFamilies to achieve the same result. I hoped to get rid of them so the possible types of `b` can't be restricted by specific instances. The solution I am currently using actually uses proxies like you suggested, but I was hoping to get rid of them, because they're inconsistent with the rest of the API that consistently uses ambiguous types. As for specific features of my problem, I will expand the question with that later today, when I have sufficient time. – Kritzefitz Feb 01 '19 at 09:37