I have a feeling I'm asking the impossible, but here it goes.
I want to associate type constructors with a fully applied version that number's the parameters at the type level with natural numbers. Here's an example ghci session with its desired use:
ghci> :kind! MKNumbered Maybe
MKNumbered Maybe :: *
= Maybe (Proxy Nat 1)
ghci> :kind! MKNumbered Either
MKNumbered Either :: *
= Either (Proxy Nat 1) (Proxy Nat 2)
To cut down on the noise of the above a little bit, essentially I get something like
Maybe >----> Maybe 1
Either >----> Either 1 2
It turns out, I can get close enough with the following type families. They actually use an extra parameter, specifying the total number of arguments, but that's ok.
type MkNumbered f n = UnU (MkNumbered_ (U f) 1 n)
type family MkNumbered_ (f :: k) (i::Nat) (n::Nat) :: j where
MkNumbered_ (U f) i i = U (f (Proxy i))
MkNumbered_ (U f) i n = MkNumbered_ (U (f (Proxy i))) (i+1) n
data U (a::k)
type family UnU f :: * where
UnU (U f) = f
The U
type is another proxy which seems necessary to get behavior I wanted. If I have a fully applied U
, i.e. U (a :: *)
I can unwrap it with UnU
.
The shortcoming of the above is that, since Proxy i :: *
, MkNumbered
can only handle constructors with *
variables. Numbering
data A (f :: * -> *) a = ...
is out, A (Proxy 1) (Proxy 2)
won't work in the Proxy 1
argument. I should be able to enhance MkNumbered
, by introducing a number of specific numbering proxies:
data NPxy1 (n :: Nat)
data NPxy2 (n :: Nat) (a :: i)
data NPxy3 (n :: Nat) (a :: i) (b :: j)
...
This should leave me with behavior like:
ghci> :kind! MKNumbered A
MKNumbered A :: *
= A (NPxy2 Nat 1) (NPxy1 Nat 2)
That helps a lot, just those three NPxy definitions probably cover most of the higher ordered kind cases. But I was wondering if there was a way to enhance this so that I could cover all k -> j -> ... -> *
cases?
Incidentally, I don't seriously hope to handle types like
data B (b::Bool) = ...
I would need something like this illegal definition:
data NPxyBool (n :: Nat) :: Bool
In any case, all the Bool
types seem to be taken already. Going further, I'd be thrilled to learn that there was a way to create some data
data UndefinedN (n :: Nat) :: forall k . k
which I called UndefinedN
since it seems like a bottom at the kind level.
Edit: Intended Use
The crux of my intended use is to query a type for the proxied parameter.
type family GetN s (a :: k) :: k
GetN (Either Int Char) (Proxy 1) ~ Int
However, I also require that if the Proxy index is some other specific type besides Proxy n
, then that type is just returned.
GetN (Either Int Char) Maybe ~ Maybe
However, any type family solution for Proxy n
makes writing family instances for GetN
with Proxy n
on the lhs illegal. I'm open to type class based solutions, where we can have:
instance (Proxy n ~ pxy, GetNat s n ~ a) => GetN s pxy a where...
but my requirement to also resolve concrete values to themselves causes conflicting instance definitions that I'm also having trouble to resolve.
The rest of this is just for information sake, but having the above I should be able to derive sub-data from my proxy parameter types. For example, filling in my definition of A
, above:
data A f a = A { unA :: f (Maybe a) }
the sub-data at unA
, as numbered parameters looks like:
type UnANums = (Proxy 1) (Maybe (Proxy 2))
I would like to derive a type family (or some other method) that creates a concrete sub-data based on an example of the super-data.
type family GetNs s (ns :: k) :: k
GetNs (A [] Int) UnANums ~ [Maybe Int]
GetNs (A (Either String) Char) UnANums ~ Either String (Maybe Char)
Ultimately, this is leading to deriving traversal signatures generically. Given a source and target contexts, for instance A f a
and A g b
, in a Generic representation I will have at the K1
nodes types like UnANums
, from which I can derive a source and target to traverse to.