purescript-heterogeneous

Why?

PureScript has a very rich type-system which lets us explicitly describe the
shape of our data in fine detail. For example, Records and row-types let us
write ergonomic, polymorphic, structurally-typed data. However, writing generic
operations over such data types often involves a lot of tedious, constraint-level
tricks. This library provides a framework which separates the traversal and
summary logic for heterogeneous data types, like Functor and Foldable do for
homogeneous types, so we can reduce the boilerplate and tricks while sharing
implementations.

How to use this library

This library exports several classes for both indexed and unindexed folds and maps.

class HMap and hmap

class HMapWithIndex and hmapWithIndex

class HFoldl and hfoldl

class HFoldlWithIndex and hfoldlWithIndex

These are similar to their homogeneous counterparts. In fact, the folds and maps we
write for heterogeneous types can be reused for homogeneous data via the App f a
newtype. That is, the following are identical:

map f [1, 2, 3]

hmap f (App [1, 2, 3])

Normal functions aren't enough for a lot of the things we need to do though, so
dispatching these is slightly different as we'll need to use PureScript's constraint
system.

The following examples will operate over Record types, since it is the most
ubiquitous heterogeneous data type in PureScript.

Example: Mapping over a homogeneous Record

Records aren't ever actually homogeneous to the type system, but sometimes all the
values end up being the same type. Mapping over an apparently homogeneous Record
is as simple as using a normal (monomorphic) function.

hmap (add 1 >>> show) { a: 1, b: 2, c: 3 }

{ a: "2", b: "3", c: "4" }

Example: Mapping over a heterogeneous Record

However, we won't be able to dispatch this without giving it a monomorphic type
signature which fixes it to a homogeneous Record like before. We want to be able to
instantiate these dictionaries for each member individually. To do that, we first
need a data type to represent our mapping function:

dataAddOneAndShow=AddOneAndShow

With this we can define an instance of Mapping, which we can use with hmap.

Helping type inference along

The compiler will not always be able to infer all types for the maps and folds
we write for heterogeneous types. That's because it will attempt to determine
an output from any combination of folding function, accumulator, and input (for
folds) or mapping function and input (for maps). This ensures that multiple
mapping and folding operations can be supported for the same underlying input,
but has the downside that the compiler will not infer these types.

You will need to provide annotations for any folding, mapping, accumulator, and
input types that are not determined in some other way.

For example, this sample showWithIndex function for showing a heterogeneous
list requires an annotation for the accumulator type: