@hackage generic-data-functions0.2.0

Familiar functions lifted to generic data types

generic-data-functions

A small Haskell library providing some funky generics that work over arbitrary Haskell data types. We handle the sums of products representation; you only need to pass a handful of definitions. Obtain simple, type-safe generic serializers/reducers and parsers for almost zero effort.

Well, OK, so really this is a generic binary serialization library. It just so happens that the generics look like foldMap and traverse.

Really?

Kind of. We only handle sequential concatenation, being cleanly represented by builtin type classes. Weirder cases like JSON parsing/serialization have more going on, and aren't sensibly discussed generically. So you are probably only going to use this with bytestrings and simple binary formats.

Why?

It is 2023. There are a number of competing parsing and serialization Haskell libraries, recently some notable high-performance ones. These are often fairly experimental. Maybe you want some generics to benchmark some real-world use case against popular libraries like binary and cereal. But maybe generics aren't provided. Shucks.

That's a shame, because a binary/cereal-esque generic binary parser or serializer doesn't have much work to do:

  • traverse the generic sum-of-products tree of the given type left to right
  • defer to the appropriate type class for base cases

Sum types necessitate a little more work. Otherwise, most such parsers and serializers look fairly comparable to each other. Why are we rewriting this stuff over and over again?

generic-data-functions provides reusable generics which have holes in for your favorite parsers and serializers. Fill out a few definitions to receive a fresh new generic instance for your own library, without all the boilerplate.

Functions

foldMap (L->R)

foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m

The user provides the a -> m dictionary via a special type class instance. Constructor fields are mapped and combined left-to-right. Sum representations are handled by mappending the constructor via a user-supplied String -> m first.

Useful for:

  • simple binary serializers which just concatenate fields together
  • reducing to a numeric value

traverse (L->R)

traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)

The user provides the f a dictionary via a special type class instance. Constructor field actions are run left-to-right. Sum representations are handled by running a constructor action first (thus requiring Monad f).

Useful for:

  • simple binary parsers which can parse generic foldMap output

Notes

Orphan instances

This library is designed to work with and around existing libraries and type classes. Thus, you will likely be dealing in orphans. Instances, that is. That's life, Jim.

Funky generics

I have made some weird design choices in this library. Here are some rationales.

Handling badness on type & term levels

I don't like silently erroring on badly-configured generics usage, e.g. asking for a function via generics for an empty data type. Originally, I made those type error, and that was that. But it meant I would write the same instance over and over again. And that requirement was hidden in a type class implementation. Really, it'd be nice if I could put such requirements directly in the types of the functions that have them.

I've done that. Now, on certain representation errors, e.g. you tried to use non-sum generics on a sum type, we runtime error instead. However, there's a separate layer for making assertions about generic representation on the type level, the use of which is highly suggested.

Note that if you like to write wrappers over generic functions to fill in certain bits of info, your job just got a lot uglier. Soz. Anything for type safety my sweet.

License

Provided under the MIT license. See LICENSE for license text.