extensible-data (empty) → 0.1
raw patch · 6 files changed
+466/−0 lines, 6 filesdep +basedep +data-lensdep +hashablesetup-changed
Dependencies added: base, data-lens, hashable, template-haskell, unordered-containers
Files
- Data/Extensible/List.lhs +73/−0
- Data/Extensible/Product.lhs +108/−0
- Data/Extensible/Sum.lhs +97/−0
- Data/Extensible/Tree.lhs +127/−0
- Setup.hs +2/−0
- extensible-data.cabal +59/−0
+ Data/Extensible/List.lhs view
@@ -0,0 +1,73 @@+% Extensible lists +% [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 lists. The values in +the list can be extensible even in other modules. + +NB: In order to use this module, you need to enable the {\tt +ScopedTypeVariables} extension, because it generates patterns with type +signatures. + +> {-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-} +> {-# LANGUAGE FunctionalDependencies #-} + +> module Data.Extensible.List ( +> ExtList(..), extList +> ) where { + +> import Control.Applicative; +> import Control.Monad; +> import Data.List; +> import Language.Haskell.TH; + +\: Utility Function. + +> bool :: x -> x -> Bool -> x; +> bool x _ False = x; +> bool _ x True = x; + +\: Implementation. The implementation is a class; its instances represent +the values to add to the list. Its only method, {\tt extListContents}, +should specify the list of values to include. The value of the first part +of the pair is irrelevant; it is only used to keep track of the type. + +> class ExtList v p | p -> v where { +> extListContents :: (p, [v]); +> }; + +The following is the TH splicer function; it is given a name of a type +which is the {\tt v} parameter of the class above, and produces an +expression which evaluates into the list of all values of all instances +that have that {\tt v} which are in scope (example: {\tt\$(extList +''List1)}). There is no guarantee to the ordering of the values, except +that values in a single instance will be in the same order relative to +each other and contiguous. + +> extList :: Name -> Q Exp; +> extList x = liftM2 (\n (ClassI _ i) -> extListInst n (ConT x) i) +> (newName "x") (reify ''ExtList); + +> extListInst :: Name -> Type -> [ClassInstance] -> Exp; +> extListInst _ _ [] = ListE []; +> extListInst n x (ClassInstance { ci_tys = [v, p] } : t) = bool id +> (InfixE (Just e) (VarE '(++)) . Just) (v == x) (extListInst n x t) +> where { +> e :: Exp; +> e = AppE (LamE [ConP '(,) [SigP WildP p, VarP n]] $ VarE n) +> (VarE 'extListContents); +> }; + +% End of document (final "}" is suppressed from printout) +\medskip\centerline{The End} +\toks0={{ + +> } -- }\bye
+ Data/Extensible/Product.lhs view
@@ -0,0 +1,108 @@+% 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
+ Data/Extensible/Sum.lhs view
@@ -0,0 +1,97 @@+% Extensible sum 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 sum types, which means +you can do something like a datatype where you can add additional +constructors even in other modules. + +> {-# LANGUAGE FunctionalDependencies, GADTs, RankNTypes, TypeFamilies #-} +> {-# LANGUAGE MultiParamTypeClasses, DeriveDataTypeable #-} + +> module Data.Extensible.Sum ( +> ExtSum(..), ExtSumC(..), SumSelector(..), callExtSum, nextExtSum, +> castExtSum, selectExtSum, lensExtSum +> ) where { + +> import Control.Applicative; +> import Control.Monad; +> import Data.Lens.Common; +> import Data.Typeable; +> import GHC.Exts (Any); +> import Unsafe.Coerce; + +\: Implementation. This implementation is based on a constrained dependent +sum type; there is a tag and then the value it corresponds to which is set +up by the instances of that class. + +> data ExtSum s where { +> ExtSum :: forall s x. ExtSumC s x => x -> ExtSumF x -> ExtSum s; +> } deriving Typeable; + +There are no special laws that need to be satisfied with instances of this +class. + +> class (Eq x, Typeable x) => ExtSumC s x | x -> s where { +> type ExtSumF x :: *; +> accessExtSum :: x -> ExtSumF x -> (s, s -> x); +> }; + +This type is used for selectors. A selector is used to convert a value of +one of the choices for an extensible sum into a value of a single type. + +> data SumSelector s v where { +> (:+?) :: forall s x v. ExtSumC s x => +> x -> (ExtSumF x -> v) -> SumSelector s v; +> }; + +> infix 0 :+?; + +\: Functions. + +{\tt callExtSum}: Can access the value of the single type which it +corresponds to. This type might even be an extensible product type. + +> callExtSum :: ExtSum s -> s; +> callExtSum (ExtSum x y) = fst (accessExtSum x y); + +{\tt nextExtSum}: Make a change in the selector. This is not generally a +functor. + +> nextExtSum :: (s -> s) -> ExtSum s -> ExtSum s; +> nextExtSum f (ExtSum x y) = let { (a, b) = accessExtSum x y; } in +> ExtSum (b $ f a) y; + +{\tt castExtSum}: Ask the constructor, and will make the value if that is +the one which is active. + +> castExtSum :: ExtSumC s x => ExtSum s -> x -> Maybe (ExtSumF x); +> castExtSum (ExtSum x y) t = unsafeCoerce y +> <$ guard (typeOf x == typeOf t && x == unsafeCoerce t); + +{\tt selectExtSum}: Given a list of selectors, take the value selected +from the extensible sum value, and apply the selector. + +> selectExtSum :: [SumSelector s v] -> ExtSum s -> Maybe v; +> selectExtSum [] _ = Nothing; +> selectExtSum ((n :+? f) : t) x = (f <$> castExtSum x n) +> <|> selectExtSum t x; + +{\tt lensExtSum}: Make a lens of an extensible sum type. + +> lensExtSum :: Lens (ExtSum s) s; +> lensExtSum = lens callExtSum $ nextExtSum . const; + +% End of document (final "}" is suppressed from printout) +\medskip\centerline{The End} +\toks0={{ + +> } -- }\bye
+ Data/Extensible/Tree.lhs view
@@ -0,0 +1,127 @@+% Extensible trees +% [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 compile-time trees. +Each node has a key, and the key of each node is of a different type. All +nodes have the same type for the value of the nodes, however. In addition, +the values of the nodes can depend on the value of the key. + +> {-# LANGUAGE MultiParamTypeClasses, GADTs, TemplateHaskell #-} +> {-# LANGUAGE FunctionalDependencies #-} + +> module Data.Extensible.Tree ( +> ExtTreeData(..), ExtTree(..), traceExtTree, normalParent, makeExtRoot, +> ExtTreeNode(..), extAncestor, extAncestorAny +> ) where { + +> import Control.Applicative; +> import Control.Monad; +> import Data.Typeable; +> import Language.Haskell.TH; + +\: Utility Function. + +> bool :: x -> x -> Bool -> x; +> bool x _ False = x; +> bool _ x True = x; + +\: Implementation. The first thing is a datatype used for the class which +is defined below. Due to the GADT, it requires that the parent of a node +also has a parent; if it is the root node you set it to its own parent. + +> data ExtTreeData v p c where { +> ExtRoot :: ExtTree v p p => ExtTreeData v p p; +> ExtNode :: ExtTree v pp p => (c -> (v, p)) -> ExtTreeData v p c; +> }; + +In this class, {\tt v} is the type of values in the tree, {\tt p} is the +parent of this node, and {\tt c} (for ``child'') is the current node. The +first method is used for traversing the tree from child to parent, and the +second method is used for traversing the tree from parent to child. + +> class Typeable c => ExtTree v p c | c -> p, p -> v where { +> treeData :: ExtTreeData v p c; +> normalChild :: p -> c; +> }; + +For the root node, the instance should always be: + +> {- +> treeData = ExtRoot; +> normalChild = id; +> -} + +The next section contains a Template Haskell code to automatically create +the instance for the root node. + +There is then a datatype which holds any key for a single node of a tree. + +> data ExtTreeNode v where { +> ExtTreeNode :: ExtTree v p c => c -> ExtTreeNode v; +> }; + +But, notice that there can be multiple roots, and a root does not even +have to be exposed from a module which defines it, as long as there is +some node which eventually leads to it. + +\: Functions. + +{\tt traceExtTree}: Make a list of values from a single node to the root. + +> traceExtTree :: ExtTree v p c => c -> [v]; +> traceExtTree c = case treeData of { +> ExtRoot -> []; +> ExtNode f -> (\(v, p) -> v : traceExtTree p) $ f c; +> }; + +{\tt normalParent}: Given a value of a key for one node of the tree, get +the corresponding value of the type of key for the parent node. + +> normalParent :: ExtTree v p c => c -> p; +> normalParent c = case treeData of { +> ExtRoot -> c; +> ExtNode f -> snd (f c); +> }; + +{\tt extAncestor}: Find the key of an ancestor of a specified node. + +> extAncestor :: (Typeable p, ExtTree v pp c) => c -> Maybe p; +> extAncestor c = cast c <|> case treeData of { +> ExtRoot -> Nothing; +> ExtNode f -> extAncestor $ snd (f c); +> }; + +{\tt extAncestorAny}: Like the above function, but in a container storing +a key of any node. + +> extAncestorAny :: Typeable p => ExtTreeNode v -> Maybe p; +> extAncestorAny (ExtTreeNode c) = extAncestor c; + +Here is a Template Haskell macro to create the instance for the root node. +Due to some fault with parsing of Template Haskell quotations, this won't +compile unless both {\tt FlexibleInstances} and {\tt UndecidableInstances} +extensions are enabled in this module. (You do not need to enable those +extensions in the module using this function.) + +> makeExtRoot :: Q Type -> Q Type -> Q [Dec]; +> makeExtRoot = liftM2 $ \v p -> [InstanceD [] ( +> AppT (AppT (AppT (ConT ''ExtTree) v) p) p +> ) [ +> ValD (VarP 'treeData) (NormalB $ ConE 'ExtRoot) [], +> ValD (VarP 'normalChild) (NormalB $ VarE 'id) [] +> ]]; + +% End of document (final "}" is suppressed from printout) +\medskip\centerline{The End} +\toks0={{ + +> } -- }\bye
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple +main = defaultMain
+ extensible-data.cabal view
@@ -0,0 +1,59 @@+-- extensible-data.cabal auto-generated by cabal init. For additional +-- options, see +-- http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html#pkg-descr. +-- The name of the package. +Name: extensible-data + +-- The package version. See the Haskell package versioning policy +-- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for +-- standards guiding when and how versions should be incremented. +Version: 0.1 + +-- A short (one-line) description of the package. +Synopsis: Sums/products/lists/trees which can be extended in other modules + +-- A longer description of the package. +Description: + Extensible lists: Add to a list at compile-time in many modules, which + do not necessarily know each other, and then collect it into a single + list in a module depending on all of them (possibly indirectly). + . + Extensible products: It is a record in which new fields can be added + anywhere including in different modules; dependent defaults are + supported, so it is still possible to make a value of such a type. + . + Extensible sums: Type with choices; new choices can be added anywhere + including in other modules (which do not need to know each other). The + operations on them are also freely extensible in the similar way. + . + Extensible trees: You can make a tree out of types, and have a value at + each node. New nodes can be added anywhere if you have access to the + node which will become the new node's parent. + +-- The license under which the package is released. +License: PublicDomain + +Category: Data + +Build-type: Simple + +-- Constraint on the version of Cabal needed to build this package. +Cabal-version: >=1.2 + + +X-Printout-Mode: PlainTeX +X-Printout-Main: Data/Extensible/*.lhs +X-Printout-Require: birdstyle.tex + + +Library + -- Modules exported by the library. + Exposed-modules: Data.Extensible.Tree, Data.Extensible.Sum, Data.Extensible.Product, Data.Extensible.List + + -- Packages needed in order to build this package. + Build-depends: + base >= 4.3 && < 4.4, + data-lens >= 2.0.1 && < 2.2, + hashable >= 1.0.1.1 && < 1.2, + template-haskell >= 2.5.0.0 && < 2.6, + unordered-containers >= 0.1.4.6 && < 0.2