Copyright | (c) Juan García Garland Marcos Viera 2019-2020 |
---|---|
License | GPL |
Maintainer | jpgarcia@fing.edu.uy |
Stability | experimental |
Portability | POSIX |
Safe Haskell | Safe |
Language | Haskell2010 |
Extensible records/row polymorphism are features not implemented in Haskell. Some other functional languages do it, like Purescript. GHC extensions allowing type level-programming allow us to encode them in Haskell.
Let us define records as a (partial) mapping from names (fields, wich are static) to values.
There are many implementations out there. This is yet another one, inspired in the HList library. It arose when programming the AspectAG library. Before, we depended on HList. Then we choose to implement a record library from scratch for two reasons:
- HList is experimental, it does not maintain a stable interface.
- We prefer a solution that fits better in our use case.
AspectAG is a library to encode type safe attribute grammars. Statically checked extensible records are used everywhere, knowing at compile time the structure of the grammar, and checking if it is well-formed.
Some example structures in AspectAG library are:
- Records: that's it, plane extensible records: Mappings from names to values.
- Attributions: mappings from attribute names to values. This is the same idea that the one for records, but we wanted to have different types for each structure, and for each label. This means that our labels must be polyinded.
- Children Records: That is a map from children to attibutions. It is a record of records.
One common way to implement a record is by using a GADT. For instance indexed by the list of pairs (label, value). We want labels polykinded, and values are usually of kind Type, what makes sense since Type is the kind of inhabited types, and records store values. However, in cases such as our children records, where we store attributions that are also represented by an indexed GADT, we would like to be able to reflect some of this structural information at the indexes of the record. This can be achieved if we are polymorphic in the kind of the types corresponding to the record fields.
Synopsis
- data Rec (c :: k) (r :: [(k', k'')]) :: Type where
- data TagField (c :: k) (l :: k') (v :: k'') where
- type family WrapField (c :: k') (v :: k) :: Type
- type family UnWrap (t :: Type) :: [(k, k')]
- untagField :: TagField c l v -> WrapField c v
- (.=.) :: WrapField c v1 ~# v2 => Label l -> v2 -> TagField c l v1
- (#) :: RequireR (OpLookup c l r) '[] v => Rec c r -> Label l -> v
- (.*.) :: Require (OpExtend c l v r) (Text "" ': ([] :: [ErrorMessage])) => TagField c l v -> Rec c r -> ReqR (OpExtend c l v r)
- type family Cmp (a :: k) (b :: k) :: Ordering
- type family ShowRec c :: Symbol
- type family ShowField c :: Symbol
- data OpLookup (c :: Type) (l :: k) (r :: [(k, k')]) :: Type where
- lookup :: Require (OpLookup c c r) ctx => Label c -> Rec c r -> ReqR (OpLookup c c r)
- data OpExtend (c :: Type) (l :: k) (v :: k') (r :: [(k, k')]) :: Type where
- data OpUpdate (c :: Type) (l :: k) (v :: k') (r :: [(k, k')]) :: Type where
- update :: (Require (OpUpdate c l v r) ctx, WrapField c v ~# v) => Label l -> v -> Rec c r -> ReqR (OpUpdate c l v r)
- emptyGenRec :: Rec c ([] :: [(k', k'')])
- module Data.GenRec.Label
Documentation
data Rec (c :: k) (r :: [(k', k'')]) :: Type where Source #
Record data structure for generic records (Internal). The c
index indicates the kind of record (for each record instance, the
user should define an index). The r
index represents the mapping
from labels to values. Labels are of kind k'
. Values are still
polykinded (k''
) since rich information can be statically
represented here (for instance, when a record of records, or a
record of Vectors is represented). k'
must implement the Cmp
family, although it is not visible here for simplicity. Records are
built putting fields ordered according to the Cmp
result of its
labels. This constructors are not intended to be used to build
records, (ConsRec
is the problematic one). Use the smart
constructors emptyRecord
and .*.
instead. We export the
constructors to pattern match on them. Although there are solutions
to hide Constructors while supporting pattern matching, we kept
it simple
Instances
(Show v, KnownSymbol l, Show (Record ((,) l' v' ': r))) => Show (Record ((,) l v ': ((,) l' v' ': r))) Source # | |
(Show v, KnownSymbol l) => Show (Record ((,) l v ': ([] :: [(Symbol, Type)]))) Source # | |
Show (Record ([] :: [(Symbol, Type)])) Source # | |
type UnWrap (Rec c r) Source # | |
Defined in Data.GenRec |
data TagField (c :: k) (l :: k') (v :: k'') where Source #
TagField | |
|
type family WrapField (c :: k') (v :: k) :: Type Source #
Given a type of record and its index, it computes the type of record inhabitants
untagField :: TagField c l v -> WrapField c v Source #
(.=.) :: WrapField c v1 ~# v2 => Label l -> v2 -> TagField c l v1 infix 4 Source #
TagField operator, note that c
will be ambiguous if not annotated.
(.*.) :: Require (OpExtend c l v r) (Text "" ': ([] :: [ErrorMessage])) => TagField c l v -> Rec c r -> ReqR (OpExtend c l v r) infixr 2 Source #
.*.
the pretty cons, hiding require
type family Cmp (a :: k) (b :: k) :: Ordering Source #
comparisson of Labels, this family is polykinded, each record-like structure must implement this family for its labels
type family ShowRec c :: Symbol Source #
Function to show the name of records (Record, Mapping, etc):
type family ShowField c :: Symbol Source #
Function to show the field of the record ("field named", "children", "tag:", etc)
data OpLookup (c :: Type) (l :: k) (r :: [(k, k')]) :: Type where Source #
Datatype for lookup (wrapper)
Instances
Require (OpError ((Text "field not Found on " :<>: Text (ShowRec c)) :$$: (((Text "looking up the " :<>: Text (ShowField c)) :<>: Text " ") :<>: ShowTE l))) ctx => Require (OpLookup c l ([] :: [(k, k')])) ctx Source # | error instance (looking up an empty record) |
Require (OpLookup' (Cmp l l') c l ((,) l' v ': r)) ctx => Require (OpLookup c l ((,) l' v ': r)) ctx Source # | wrapper instance |
type ReqR (OpLookup c l ([] :: [(k, k')])) Source # | |
Defined in Data.GenRec | |
type ReqR (OpLookup c l ((,) l' v ': r)) Source # | |
Defined in Data.GenRec |
lookup :: Require (OpLookup c c r) ctx => Label c -> Rec c r -> ReqR (OpLookup c c r) Source #
The lookup function. Given a Label
and a Record
, it returns
the field at that position. It raises a custom type
error if there is no field labelled with l.
data OpExtend (c :: Type) (l :: k) (v :: k') (r :: [(k, k')]) :: Type where Source #
extension operator (wrapper)
Instances
Require (OpExtend c l v ([] :: [(k, k')])) ctx Source # | extending an empty record |
Require (OpExtend' (Cmp l l') c l v ((,) l' v' ': r)) ctx => Require (OpExtend c l v ((,) l' v' ': r)) ctx Source # | wrapper instance |
type ReqR (OpExtend c l v ([] :: [(k, k')])) Source # | |
Defined in Data.GenRec | |
type ReqR (OpExtend c l v ((,) l' v' ': r)) Source # | |
Defined in Data.GenRec |
data OpUpdate (c :: Type) (l :: k) (v :: k') (r :: [(k, k')]) :: Type where Source #
update operator (wrapper)
Instances
Require (OpError ((Text "field not Found on " :<>: Text (ShowRec c)) :$$: ((Text "updating the " :<>: Text (ShowField c)) :<>: ShowTE l))) ctx => Require (OpUpdate c l v ([] :: [(k, k')])) ctx Source # | error instance |
Require (OpUpdate' (Cmp l l') c l v ((,) l' v' ': r)) ctx => Require (OpUpdate c l v ((,) l' v' ': r)) ctx Source # | wrapper instance |
type ReqR (OpUpdate c l v ([] :: [(k, k')])) Source # | |
type ReqR (OpUpdate c l v ((,) l' v' ': r)) Source # | |
Defined in Data.GenRec |
update :: (Require (OpUpdate c l v r) ctx, WrapField c v ~# v) => Label l -> v -> Rec c r -> ReqR (OpUpdate c l v r) Source #
The update function. Given a Label
and value, and a Record
containing this label, it updates the value. It could change its
type. It raises a custom type error if there is no field
labelled with l.
emptyGenRec :: Rec c ([] :: [(k', k'')]) Source #
The empty Record. Note that it is polymorphic on the kind of record c
.
module Data.GenRec.Label