I have a library that currently demands of users that they provide a helper function with type:
tEnum :: (KnownNat n) => MyType -> Finite n
so that the library implementation can use a very efficient sized vector representation of a function with type:
foo :: MyType -> a
(MyType
is discrete and finite.)
Assuming that deriving a Generic
instance for MyType
is possible, is there a way to generate tEnum
automatically, thus lifting that burden from my library's users?
I would also like to go the other way; that is, automatically derive:
tGen :: (KnownNat n) => Finite n -> MyType
I have something working for at least the
tEnum
side of things. Since you did not specify your representation ofFinite
I used my ownFinite
andNat
.I have included a full code snippet with an example at the bottom of the post, but will only discuss the generic programming parts, leaving out the reasonably standard construction of Peano arithmetic and various useful theorems about it.
A typeclass is used to keep track of things that can be converted into/out of these finite enums. The important bit here is the default type signatures and the default definitions: these mean that if someone derives
EnumFin
for a class derivingGeneric
, they don't have to actually write any code, as these defaults will be used. The defaults use methods from another class, which is implemented for the various kinds of things thatGHC.Generics
can produce. Notice that both the normal and the default signatures use(n ~ ...) => ... n
instead of writing the size of theFinite
directly in the type signature; this is because GHC will otherwise detect that the default signatures don't have to match the regular signatures (in the case of a class implementation that definesSize
but notfromFin
ortoFin
):There are actually also a couple of other utility methods in the class. These are used by the actual generic implementation to get the minimum/maximum
Finite n
produced by an implementation (0
andn
) without having to use more typeclasses & propagateKnownNat
-style constraints:The class declaration for the generic class is fairly simple; note however that its parameter is kind
* -> *
, not*
:This generics class now must be implemented for each of the relevant generic constructors. For example,
U1
is a very simple one, referring to a constructor without fields, which is just encoded as theFinite
number0
::*:
is used to combine individual fields, so both parts need to be encoded (it encodeslhs*(m+1)+rhs
wherem
is the max value of the rhs)::+:
on the other hand is used when representing sums, and so must be able to encode either of its constituents (it encodes the left hand side as0..n
and the right asn+1...n+1+m
):There is also an important instance for a single constructor field, which requires that the contained type also implement
EnumFin
:Finally, it is necessary to implement the
M1
constructor, which is used to attach metadata to the generic tree, and which we don't care about at all here:For completeness, here is a complete file that defines all of the
Nat
/Finite
infrastructure used above and exhibits using theGeneric
implementation: