packages feed

extensible-data-0.1: Data/Extensible/Product.lhs

% Extensible product types
% [Public domain]

\input birdstyle

\birdleftrule=1pt
\emergencystretch=1em

\def\hugebreak{\penalty-600\vskip 30pt plus 8pt minus 4pt\relax}
\newcount\chapno
\def\: #1.{\advance\chapno by 1\relax\hugebreak{\bf\S\the\chapno. #1. }}

\: Introduction. This module implements extensible product types, which
means you can have like a record, and add fields to this record even in
different modules. Dependent defaults are supported.

> {-# LANGUAGE FunctionalDependencies, GADTs, TypeFamilies #-}
> {-# LANGUAGE MultiParamTypeClasses, DeriveDataTypeable #-}

> module Data.Extensible.Product (
>   ExtProd, ExtProdC(..), ProdConstructor(..), emptyExtProd, getExtProd,
>   putExtProd, lensExtProd, constructExtProd
> ) where {

> import Control.Applicative;
> import Data.Hashable;
> import Data.HashMap.Lazy (HashMap);
> import qualified Data.HashMap.Lazy as H;
> import Data.Lens.Common;
> import Data.Typeable;
> import GHC.Exts (Any);
> import Unsafe.Coerce;

\: Implementation. It is implemented using a hash map, with types as keys.
For it to be extensible there has to be default values. It allows default
values to be based on a single value of the type that indexes the {\tt
ExtProd} type, which is used to tell what kind of record it is.

> data ExtProd p where {
>   ExtProd :: p -> HashMap FieldSelector Any -> ExtProd p;
> } deriving Typeable;

This implements hashable type representations in private, so that it will
not conflict with other modules.

> newtype FieldSelector = FieldSelector TypeRep deriving Eq;

> instance Hashable FieldSelector where {
>   hash (FieldSelector x) = hash (show x);
> };

Instances should ignore the value of the first parameter of the {\tt
defaultExtProd} method; it is used only for knowing the type (so it will
be OK to pass {\tt undefined} as the first parameter).

> class Typeable x => ExtProdC p x | x -> p where {
>   type ExtProdF x :: *;
>   defaultExtProd :: x -> p -> ExtProdF x;
> };

In addition, we will have a constructor-like datatype for extensible
products, which is similar to an extensible sum type, and can be used in
a list to construct a value of the type.

> data ProdConstructor p where {
>   (:*=) :: ExtProdC p x => x -> ExtProdF x -> ProdConstructor p;
> };

> infix 0 :*=;

\: Functions.

{\tt emptyExtProd}: Make a value of extensible product type with all
values set to the defaults.

> emptyExtProd :: p -> ExtProd p;
> emptyExtProd = flip ExtProd H.empty;

{\tt getExtProd}: Get the value of a field.

> getExtProd :: ExtProdC p x => ExtProd p -> x -> ExtProdF x;
> getExtProd (ExtProd p m) f = unsafeCoerce $ H.lookupDefault
>  (unsafeCoerce $ defaultExtProd f p) (FieldSelector $ typeOf f) m;

{\tt putExtProd}: Set the value of a field.

> putExtProd :: ExtProdC p x => ExtProd p -> x -> ExtProdF x -> ExtProd p;
> putExtProd (ExtProd p m) f v = ExtProd p $ H.insert
>  (FieldSelector $ typeOf f) (unsafeCoerce v) m;

{\tt lensExtProd}: Make a lens of an extensible product type.

> lensExtProd :: ExtProdC p x => x -> Lens (ExtProd p) (ExtProdF x);
> lensExtProd f = lens (flip getExtProd f) (\v x -> putExtProd x f v);

{\tt constructExtProd}: Construct a value of an extensible product type.

> constructExtProd :: p -> [ProdConstructor p] -> ExtProd p;
> constructExtProd p l = ExtProd p $ H.fromList (constructorToKV <$> l);

> constructorToKV :: ProdConstructor p -> (FieldSelector, Any);
> constructorToKV (k :*= v) = (FieldSelector $ typeOf k, unsafeCoerce v);

% End of document (final "}" is suppressed from printout)
\medskip\centerline{The End}
\toks0={{

> } -- }\bye