There are lots of ways to do this, conceptually. Unfortunately, most of them don't actually work. Alas!
First, as a functional programmer, I'd wager that this is what you really want to write:
instance Listable (\a -> (a, a)) where
asList (p, q) = [p,q]
Unfortunately type-level lambdas don't exist. We could write a named version of the above lambda using a type synonym:
type Same2 f a = f a a
instance Listable (Same2 (,)) where { ... }
That's not allowed either, because the type synonym isn't fully-applied. We could instead imagine the type class taking an extra argument that would describe how to apply the type variables:
class Listable app f where
asList :: app f a -> [a]
instance Listable __ Maybe where { ... }
instance Listable __ (,) where { ... }
Without even thinking about what app
might be, this also fails because we don't have a consistent kind for the f
parameter.
Moving on to things that actually work, I think the most common way is to wrap the type synonym approach inside a newtype
, then just deal with the wrapping and unwrapping that involves.
newtype Same2 f a = Same2 (f a a)
instance Listable (Same2 (,)) where { ... }
It's workable, if a bit ugly. You can also define type constructor composition and other toys this way, then go nuts with type-level points-free expressions buried under a pile of hoop-jumping boilerplate.
As a final approach, you can also encode the lambda-style approach above "in reverse", going from the fully-applied version, to the single type parameter:
class Listable t where
type ListableElem t :: *
asList :: t -> [ListableElem t]
Being able to do this sort of thing is one of the main motivations for type families. The same thing can be expressed with MPTCs and fundeps, but it's 1) equivalent and 2) much uglier, so I won't bother writing it out.