Using a default implementation of typeclass method to omit an argument

Viewed 75

I want to be able to define a (mulit-parameter-) typeclass instance whose implementation of the class's method ignores one of its arguments. This can be easily done as follows.

instance MyType MyData () where
    specific _ a = f a

As I'm using this pattern in several places, I tried to generalize it by adding a specialized class method and adequate default implementations. I came up with the following.

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

class MyType a b where
    specific :: b -> a -> a
    specific = const dontCare
    dontCare :: a -> a
    dontCare = specific (undefined :: b)
    {-# MINIMAL specific | dontCare #-}

This however yields the error Could not deduce (MyType a b0) arising from a use of ‘dontCare’ [..] The type variable ‘b0’ is ambiguous. I don't see why the latter should be the case with the type variable b being scoped from the class signature to the method declaration. Can you help me understand the exact problem that arises here?

Is there another reasonable way to achieve what I intended, namely to allow such trimmed instances in a generic way?

1 Answers

The problem is in the default definition of specific. Let's zoom out for a second and see what types your methods are actually given, based on your type signatures.

specific :: forall a b. MyType a b => b -> a -> a
dontCare :: forall a b. MyType a b => a -> a

In the default definition of specific, you use dontCare at type a -> a. So GHC infers that the first type argument to dontCare is a. But nothing constrains its second type argument, so GHC has no way to select the correct instance dictionary to use for it. This is why you ended up needing AllowAmbiguousTypes to get GHC to accept your type signature for dontCare. The reason these "ambiguous" types are useful in modern GHC is that we have TypeApplications to allow us to fix them. This definition works just fine:

class MyType a b where
    specific :: b -> a -> a
    specific = const (dontCare @_ @b)
    dontCare :: a -> a
    dontCare = specific (undefined :: b)
    {-# MINIMAL specific | dontCare #-}

The type application specifies that the second argument is b. You could fill in a for the first argument, but GHC can actually figure that one out just fine.

Related