shapely-data-0.1: Generics using @(,)@ and @Either@, with algebraic operations and typed conversions

Safe HaskellNone





You can generate a Shapely instance for custom types with, e.g.

 data L a = Snoc (L a) a | Lin
 deriveShapely ''L

Then you can use coerce or massage to convert between types, or see the functions in Data.Shapely.Normal for functions for composing and transforming Normal form types, including algebraic operations and coversion functions.

class Exponent (Normal a) => Shapely a whereSource

Instances of the Shapely class have a Normal form representation, made up of (,), () and Either, and functions to convert from a and back to a again.

Associated Types

type Normal a Source

A Shapely instances "normal form" representation, consisting of nested product, sum and unit types. Types with a single constructor will be given a Product Normal representation, where types with more than one constructor will be Sums.

See the documentation for deriveShapely, and the instances defined here for details.


from :: a -> Normal aSource

to :: Normal a -> aSource

constructorsOf :: a -> Normal a :=>-> aSource

Return a structure capable of rebuilding a type a from its Normal representation (via fanin).

This structure is simply the data constructor (or a Product of constructors for Sums), e.g. for Either:

 constructorsOf _ = (Left,(Right,()))


 'fanin' (constructorsOf a) (from a) == a


Shapely Bool 
Shapely Ordering 
Shapely () 
Shapely [a] 
Shapely (Maybe a0) 
Shapely (Either x y) 
Shapely (x, y)

Note, the normal form for a tuple is not itself

Shapely (a0, b0, c0) 
Shapely (a0, b0, c0, d0) 
Shapely (a0, b0, c0, d0, e0) 
Shapely (a0, b0, c0, d0, e0, f0) 
Shapely (a0, b0, c0, d0, e0, f0, g0) 
Shapely (a0, b0, c0, d0, e0, f0, g0, h0) 
Shapely (a0, b0, c0, d0, e0, f0, g0, h0, i0) 
Shapely (a0, b0, c0, d0, e0, f0, g0, h0, i0, j0) 
Shapely (a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0) 
Shapely (a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0) 
Shapely (a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0) 
Shapely (a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0) 
Shapely (a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0) 

class NormalConstr t ~ (,) => Product t Source

A Product is a list of arbitrary terms constructed with (,), and terminated by () in the snd. e.g.

 prod = (1,(2,(3,())))


Product () 
Product ts => Product (a, ts) 

class NormalConstr e ~ Either => Sum e Source

A Sum is a non-empty list of Products constructed with Either and terminated by a Product type on the Right. e.g.

 s = (Right $ Left (1,(2,(3,())))) :: Either (Bool,()) (Either (Int,(Int,(Int,()))) (Char,()))

To simplify type functions and class instances we also define the singleton sum Only.


Product t => Sum (Only t) 
(Product t, Sum (Either b c)) => Sum (Either t (Either b c)) 
(Product t, Product (a, b)) => Sum (Either t (a, b)) 
Product t => Sum (Either t ()) 

Deriving Shapely instances automatically

deriveShapely :: Name -> Q [Dec]Source

Generate a Shapely instance for the type passed as argument nm. Used like:

 $(deriveShapely ''Tree)  -- two single-quotes reference a TH "Name"

The algorithm used here to generate the Normal instance is most easily described syntactically:

  • Constructors are replaced with (), which terminate (rather than start) a product
  • Product terms are composed with nested tuples, e.g. Foo a b c ==> (a,(b,(c,())))
  • The | in multiconstructor (Sum) type declarations is replaced with Either, with a nesting like the above

Note that a Product type in the Right place terminates a composed Sum, while a () in the snd place terminates the composed terms of a Product.

Typed conversion functions

class (Shapely a, Shapely b) => Isomorphic a b whereSource

Two types a and b are isomorphic, by this definition, if they have the same number and ordering of constructors, and where Product terms are identical or a corresponding recursive a and b.


coerce :: a -> bSource

Convert a possibly direct-recursive type a to an isomorphic type b. This is defined:

 coerce a = 'coerceWith' ('unappliedOf' a, ()) a

See massage for a more powerful and flexible conversion function supporting direct recursion.


(Unapplied k (Proxy * txy) t, CoercibleWith (Proxy k t, ()) txy b) => Isomorphic txy b

We use Unapplied to recurse on all occurrences of the base type t, regardless of its parameters.

class (SpineOf ts, Shapely a, Shapely b) => CoercibleWith ts a b whereSource

A Shapely type a coercible to b where types in the spine ts are recursively coerceWith-ed to b.


coerceWith :: ts -> a -> bSource

Convert a type a to an isomorphic type b, where the Proxy types in ts define the recursive structure of a. See SpineOf.

These terms will be converted to the target type when they appear as top-level product terms or in nested Functor applications.


(Shapely a, Shapely b, CoercibleNormalWith ts (Normal a) (Normal b)) => CoercibleWith ts a b 

class Massageable a b whereSource

DISCLAIMER: this function is experimental (although it should be correct) and the behavior may change in the next version, based on user feedback. Please see list of limitations below and send feedback if you have any.

A class for typed, principled, "fuzzy coercions" between types. See also MassageableNormal.

This works as follows (or see examples below):

  • All Products in the source a must be mappable unambiguously to exactly one product in the target b, according to the rules below. This is a total function, and all product terms in the source are preserved.
  • Products in a come in two flavors which are mapped differently onto b: if a source product contains all uniquely-typed terms we treat it as a type-indexed product (TIP) and its terms will be freely shuffled to match its target; otherwise we consider the ordering of product terms to be significant and require a target product with the same ordering. The mapping between terms in source and target products is a bijection.
  • We map source product subterm as with target b subterms, by recursively applying massage (this is the only exception to the above, and the only place where we inspect Product subterms).
  • When the source a is a Sum this conversion may be surjective wrt the product mappings, i.e. multiple source "constructors" may map to the same target constructor. But again the individual mappings must be unambiguous.

Here are some examples:

 data Tsil a = Snoc (Tsil a) a | Lin
           deriving Eq
 deriveShapely ''Tsil
 truth = massage "123" == Snoc (Snoc (Snoc Lin '3') '2') '1'

One limitation is we don't support a way to handle recursive structures beyond top-level direct recursion (e.g. mutually-recusrive pairs of types). And unlike coerce functor type-applied recursive terms are not supported.

Any feedback on the above behavior would be greatly appreciated.


massage :: a -> bSource


(Shapely a, Shapely b, MassageableNormalRec a b (Normal a) (Normal b)) => Massageable a b 

($$) :: (Shapely a, Shapely b) => (Normal a -> Normal b) -> a -> bSource

Apply a function on the Normal representation of a type to an ordinary value.

 ($$) f = to . f . from