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
tEnumside of things. Since you did not specify your representation ofFiniteI used my ownFiniteandNat.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
EnumFinfor 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.Genericscan produce. Notice that both the normal and the default signatures use(n ~ ...) => ... ninstead of writing the size of theFinitedirectly 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 definesSizebut notfromFinortoFin):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 nproduced by an implementation (0andn) 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,
U1is a very simple one, referring to a constructor without fields, which is just encoded as theFinitenumber0::*:is used to combine individual fields, so both parts need to be encoded (it encodeslhs*(m+1)+rhswheremis 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..nand 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
M1constructor, 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/Finiteinfrastructure used above and exhibits using theGenericimplementation: