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?
The abstract class can have a state, and its methods can access the implementation's state. Although default methods are allowed in an interface, they can't access the implementation's state. Any logic we write in the default method should be with respect to other methods of the interface — those methods will be independent of the object's state.
Default methods − Unlike other abstract methods these are the methods that can have a default implementation. If you have a default method in an interface, it is not mandatory to override (provide body) it in the classes that are already implementing this interface.
With the interface default implementation that is not changing, they will be explicit.
Java 8 introduces default method so that List/Collection interface can have a default implementation of forEach method, and the class implementing these interfaces need not implement the same. public interface vehicle { default void print () { System.out.println ("I am a vehicle!"); } }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With