12
votes

I want to write a Convertible instance for a Haskell type to its C representation

It looks like this:

instance Convertible Variable (IO (Ptr ())) where

Now GHC complains:

 Illegal instance declaration for `Convertible
                                    Variable (IO (Ptr ()))'
  (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 -XFlexibleInstances if you want to disable this.)
In the instance declaration for `Convertible Variable (IO (Ptr ()))'

I thought Flexible Instances were needed if you had free types in your instance declaration, but this is not the case. I can get it to compile when adding the right pragma, but can anyone explain why I need this?

2
because this "All instance types must be of the form (T a1 ... an) where a1 ... an are distinct type variables" is the standard (Haskell 2010). If you need things that go beyond the standard, you must use extensions.Ingo
Sure they are distinct, but neither () nor Ptr () nor IO (Ptr ()) are type variablesIngo
FWIW, FlexibleInstances is one of the most harmless language extensions that exist. Just enable it.kosmikus
@kosmikus I feel like there should be a list of the most common extensions and their level of harmlessness somewhere. As a beginner, I feel weirded out every time GHC asks me to turn an extension on. EDIT: Apparently there is! Courtesy of supki in #haskell: List of extensionskqr
@kqr Well, I certainly have my own list :) The list you quote is at least problematic because it lists extensions that are difficult to implement and still changing even today such as TypeFamilies on the same level as utter trivialities such as EmptyDataDecls. Both might be good extensions, but if you're concerned about bitrot and stability, that's unfortunately not the only concern.kosmikus

2 Answers

19
votes

Yes you need flexible instances. Without it, all of your typeclass instance would have to look like

instance Foo (Maybe a) where
instance Foo (IO a)    where
instance Foo (Either a b) where

If you want to do anything other than TypeConstructor a1 a2 a3 ... (and as have to be type variables) than you need flexible instances. But it's one of the most common language extensions, don't sweat using it.

4
votes

You can often avoid FlexibleInstances in cases like this. There are two general approaches:

Use an auxiliary class

instance ConvIO a => Convertible Variable (IO a) where
  method = methodIO

class ConvIO p where
  methodIO :: ...
  -- similar to the signature for the Convertible method,
  -- but with Variable and IO baked in

instance ConvIOPtr u => ConvIO (Ptr u) where
   methodIO = methodIOPtr

class ConvIOPtr u where
   methodIOPtr :: ...

instance ConvIOPtr () where ...

This approach works well when you need several instances headed by the same constructor.

Use an equality constraint

Turn on GADTs or TypeFamilies and write

instance a ~ Ptr () => Convertible Variable (IO a) where ...

This approach tends to help a lot with type inference, but only makes sense if you only need one instance headed by IO.

You can mix and match

You can use an auxiliary class to go through IO, and then an equality constraint to go through Ptr.

instance u ~ () => ConvIO (Ptr u) where ...

Or you can use an equality constraint to go through IO and an auxiliary class to go through Ptr:

instance (a ~ Ptr u, ConvIOPtr u) => Convertible Variable (IO a)

Where these won't work

If you need an instance one of whose arguments is a type variable, then you really can't avoid FlexibleInstances at all. You might be able to work around the problem using a newtype, but it's not worth the trouble.