I recently added the
ListToRow type classes to the 2.3.0
release of the typelevel-prelude under the
module. The idea with these two type classes is to be able to compute at the
type-level with rows of types. This is achieved by converting to/from a
This describes that entries in a
RowList are pairs of symbols and types.
RowToList type class converts from a row of types to a
RowList. It is
solved by the compiler (as of version 0.11.6) and is defined as:
It extracts the collection of entries in a closed row of types. The list of entries is sorted by label but preserves order of duplicates.
Here are a few examples with a given input row and the computed
The (almost) inverse of this operation is
ListToRow - which takes a
and computes a row of types from it. This type class is straight-forwardly
defined by recursively applying a
Note that a
RowList need not have sorted keys for
ListToRow to produce a
row. The list produced by
RowToList row list should produce the same
when passed to
ListToRow list row, but not necessarily the other way around.
A good demonstration of the kinds of things we can do with these type classes
applyRecord function. This will accept a record of functions and a
record of inputs, producing a record of outputs. Such that each key in the
input and the output may have distinct types.
First we’ll write out the type of
Here I’m using
io to indicate input, output, and functions from
input to output; respectively.
Now for the type class:
Notice all those functional dependencies. To be able to compute the any of these types we need to know the other two.
There’s only one instance of the above type class, and it’s a fairly
straight-forward conversion of each of the rows to
RowList, then delegate to
ApplyRowList type class, and finally convert back to row. The conversions
are constrained to be inverses of each other.
ApplyRowList is (as you might expect) a version of
instead of working with rows of types, it works with
Almost exactly the same as the previous type class, just working at a different kind.
Now that we have list representations of the rows, we can recursively process
them! The first case to deal with is
Nil. Remember that all the records
must have the same keys. So when we hit
Nil, they must all be
Next up is the
Cons case. Again all three lists must be
Cons at the same
time. Not only that, but they must have the same keys in the same order.
Notice the relationship between the entries here:
i -> o,
As an instance constraint, we recursively compute on the tails of each
…and we’re done! At least the type computation part. We could write
applyRecord with the FFI - which I’ll leave out as an implementation detail.
To demonstrate that this works, here are a few examples:
Hopefully this has given you some insight into how the
ListToRow type classes have given us super powers in dealing with rows. For
example, we can now define
Eq instances for records!
I look forward to seeing all the fun things people do with this :)
In the previous version of this post I had the fundeps as:
This was incorrect, for example knowing just the input can’t tell you the output.