Why fcf-containers? It could be used in e.g. to
- increase the safety measures of runtime methods,
- pre-calculate complex things once on compile time and not every time the executable is run,
- provide users a way to choose between different algorithms for solving a problem based on problem instance properties (e.g. local vs network, or small vs large) known in advance.
Why fcf-like? The kind of signatures used for functions might be easier to read for some people and the ability to apply partially a function is very nice tool to have. The techniques that allows this are defunctionalization, encoding the functions with empty data types and the use of open type family to
Eval the constructed expressions. The following is from blog-post introducing the first-class-families:
Encoding type families with type constructors allows them to be passed around without applying them, which is not possible in their original form.
The original form refers to type family declaration that has to be fully evaluated.
As an example, consider sum of Nats given in a type-level list. Fcf-containers add type-level functions so that you can calculate this even with catamorphism.
Note that the algebra is defined only at type-level. We use the type-level base functor
data SumAlg :: Algebra (ListF Nat) Nat type instance Eval (SumAlg 'NilF) = 0 type instance Eval (SumAlg ('ConsF a b)) = a TL.+ b
After that, we can use it on
ghci with the provided
> :kind! Eval (Cata SumAlg =<< ListToFix '[1,2,3,4]) > = 10
Or, if you like AoC problems, there is one example showing how to calculate something on type-level and use the result (see orbits example).
In addition to the motivation given above, and because finding and laying out good reasons is hard, if you find out some, could you please e.g. drop and show those here?
This is my first ever published package so I’d like to welcome any feedback you may have.