I've written a newtype Const3
that's very similar to Const
, but contains the first of three given type arguments:
newtype Const3 a b c = Const3 { getConst3 :: a }
I can define very many useful instances for this newtype, but I'd have to do it all myself.
However, the function I'm applying on the type level resembles the function
\a b c -> a
which @pl
tells me is equivalent to const . const
.
Both (.)
and const
have matching newtype wrappers: Compose
and Const
. So I figured I'd be able to write:
type Const3 = Compose Const Const
And inherit useful instances automatically, such as:
instance Functor (Const m)
instance (Functor f, Functor g) => Functor (Compose f g)
-- a free Functor instance for Const3!
But GHC disagrees:
const3.hs:5:23:
Expecting one more argument to ‘Const’
The first argument of ‘Compose’ should have kind ‘* -> *’,
but ‘Const’ has kind ‘* -> * -> *’
In the type ‘Compose Const Const’
In the type declaration for ‘Const3’
This seems to be related to the kinds of Compose
and Const
:
*Main> :k Compose
Compose :: (* -> *) -> (* -> *) -> * -> *
*Main> :k Const
Const :: * -> * -> *
So after a little bit of searching, I found that there's a GHC extension called PolyKinds
that allows me to do something like:
{-# LANGUAGE PolyKinds #-}
newtype Compose f g a = Compose { getCompose :: f (g a) }
newtype Const a b = Const { getConst :: a }
And as if by magic the kinds are right:
*Main> :k Compose
Compose :: (k -> *) -> (k1 -> k) -> k1 -> *
*Main> :k Const
Const :: * -> k -> *
But I still can't compose them to write Const3 = Compose Const Const
.
const3.hs:12:23:
Expecting one more argument to ‘Const’
The first argument of ‘Compose’ should have kind ‘* -> *’,
but ‘Const’ has kind ‘* -> k0 -> *’
In the type ‘Compose Const Const’
In the type declaration for ‘Const3’
What gives? Is there some clever way to do this, so I can reap the benefits of inheriting the Functor
etc. instances from Const
and Compose
?
(As a side note, the original thought that led me to
Const3
was writing:newtype Const3 a b c = Const3 { getConst3 :: a } instance Monoid m => Category (Const3 m) where id = Const3 mempty Const3 x . Const3 y = Const3 (mappend x y)
capturing the idea that a monoid is a single-object category. It would be nice if there's a solution that still allows me to write the above instance somehow.)
The thing that's confusing—or, at least, the thing that confused me—is that
*
acts like a concrete type, not a type variable. So withoutPolyKinds
,Compose
has a type that's more like:Crucially, we can't replace an
A
withA -> A
because they'd be different types, so, by the same logic, we can't replace*
with* -> *
either.Even with
PolyKinds
, the kinds still aren't right. In particular,Compose
expects(k -> *)
as its first argument and you're trying to give it(k -> (k2 -> *))
.The reason you're forced to return a
*
kind is because you're usingnewtypes
, and newtypes have to return a concrete type (ie of kind*
). I tried to overcome this by turningCompose
into a type synonym which finally had exactly the kind we want (withPolyKinds
):However, using this still gave me a similar error, and I'm not certain if we can get it to work properly. The problem arose because applying
Compose
to the firstConst
gives us a kind with a*
in it, probably because on limitations of type aliases like this: