https://wiki.haskell.org/api.php?action=feedcontributions&user=Atzeus&feedformat=atomHaskellWiki - User contributions [en]2021-01-23T18:54:46ZUser contributionsMediaWiki 1.27.4https://wiki.haskell.org/index.php?title=CTRex&diff=57294CTRex2013-12-09T08:53:12Z<p>Atzeus: /* Row polymorphism */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>r = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" r </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural''': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: ((r :! "x") ~ Double, (r :! "y") ~ Double) =><br />
Rec r -> Rec ("norm" ::= Double :| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Now the type <hask> f </hask> reads as follows: Given some record with row <hask>r</hask>where x and y have type Double, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>. The type that GHC infers is even more general :<br />
<br />
<haskell><br />
f :: (Floating t, (r :! "y") ~ t, (r :! "x") ~ t) =><br />
Rec r -> Rec (Extend "norm" (r :! "x") r)<br />
</haskell><br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
As another use case for duplicate labels: consider implementing an interpreter for some embedded DSL, and you want to carry the <br />
state of the variables in the an extensible record. Declaring a new variable in the embedded language then<br />
causes us to extend the record. Since the embedded language allows shadowing (as most languages do), we can simply<br />
extend the record, we do not have to jump through hoops to make sure there are no duplicate labels. Once the variable<br />
goes out of scope, we restrict the record with the label to bring the old "variable" into scope.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> <br />
renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') =><br />
Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the sequence of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
<br />
One might wonder why we use a Sequence here instead of a list, since we only query the head and prepend. This is implement record merge (.+) more efficiently since we can then use <hask>(><)</hask> (O(1)) instead of <hask>(++)</hask> (O(n)), as follows:<br />
<br />
<haskell><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)<br />
(OR l) .++ (OR r) = OR $ M.unionWith (><) l r<br />
</haskell><br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nicer syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57281CTRex2013-12-06T10:16:24Z<p>Atzeus: /* Records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>r = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" r </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural''': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
As another use case for duplicate labels: consider implementing an interpreter for some embedded DSL, and you want to carry the <br />
state of the variables in the an extensible record. Declaring a new variable in the embedded language then<br />
causes us to extend the record. Since the embedded language allows shadowing (as most languages do), we can simply<br />
extend the record, we do not have to jump through hoops to make sure there are no duplicate labels. Once the variable<br />
goes out of scope, we restrict the record with the label to bring the old "variable" into scope.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> <br />
renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') =><br />
Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the sequence of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
<br />
One might wonder why we use a Sequence here instead of a list, since we only query the head and prepend. This is implement record merge (.+) more efficiently since we can then use <hask>(><)</hask> (O(1)) instead of <hask>(++)</hask> (O(n)), as follows:<br />
<br />
<haskell><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)<br />
(OR l) .++ (OR r) = OR $ M.unionWith (><) l r<br />
</haskell><br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nicer syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57280CTRex2013-12-06T10:15:34Z<p>Atzeus: /* Records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>r = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" r </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural''': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
As another use case for duplicate labels: consider implementing an interpreter for some embedded DSL, and you want to carry the <br />
state of the variables in the an extensible record. Declaring a new variable in the embedded language then<br />
causes us to extend the record. Since the embedded language allows shadowing (as most languages do), we can simply<br />
extend the record, we do not have to jump through hoops to make sure there are no duplicate labels. Once the variable<br />
goes out of scope, we restrict the record with the label to bring the old "variable" into scope.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> <br />
renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') =><br />
Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
<br />
One might wonder why we use a Sequence here instead of a list, since we only query the head and prepend. This is implement record merge (.+) more efficiently since we can then use <hask>(><)</hask> (O(1)) instead of <hask>(++)</hask> (O(n)), as follows:<br />
<br />
<haskell><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)<br />
(OR l) .++ (OR r) = OR $ M.unionWith (><) l r<br />
</haskell><br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nicer syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57279CTRex2013-12-06T10:15:22Z<p>Atzeus: /* Records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>r = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" r </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural''': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
As another use case for duplicate labels: consider implementing an interpreter for some embedded DSL, and you want to carry the <br />
state of the variables in the an extensible record. Declaring a new variable in the embedded language then<br />
causes us to extend the record. Since the embedded language allows shadowing (as most languages do), we can simply<br />
extend the record, we do not have to jump through hoops to make sure there are no duplicate labels. Once the variable<br />
goes out of scope, we restrict the record with the label to bring the old "variable" into scope.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> <br />
renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') =><br />
Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
One might wonder why we use a Sequence here instead of a list, since we only query the head and prepend. This is implement record merge (.+) more efficiently since we can then use <hask>(><)</hask> (O(1)) instead of <hask>(++)</hask> (O(n)), as follows:<br />
<br />
<haskell><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)<br />
(OR l) .++ (OR r) = OR $ M.unionWith (><) l r<br />
</haskell><br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nicer syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57278CTRex2013-12-06T10:14:46Z<p>Atzeus: /* Records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>r = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" r </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural''': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
As another use case for duplicate labels: consider implementing an interpreter for some embedded DSL, and you want to carry the <br />
state of the variables in the an extensible record. Declaring a new variable in the embedded language then<br />
causes us to extend the record. Since the embedded language allows shadowing (as most languages do), we can simply<br />
extend the record, we do not have to jump through hoops to make sure there are no duplicate labels. Once the variable<br />
goes out of scope, we restrict the record with the label to bring the old "variable" into scope.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> <br />
renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') =><br />
Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
One might wonder why we use a Sequence here instead of a list, since we only query the head and prepend. This is implement record merge (.+) more efficiently since we can then use (><) (O(1)) instead of (++) (O(n)), as follows:<br />
<br />
<haskell><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)<br />
(OR l) .++ (OR r) = OR $ M.unionWith (><) l r<br />
</haskell><br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nicer syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57277CTRex2013-12-06T10:14:10Z<p>Atzeus: /* Records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>r = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" r </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural''': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
As another use case for duplicate labels: consider implementing an interpreter for some embedded DSL, and you want to carry the <br />
state of the variables in the an extensible record. Declaring a new variable in the embedded language then<br />
causes us to extend the record. Since the embedded language allows shadowing (as most languages do), we can simply<br />
extend the record, we do not have to jump through hoops to make sure there are no duplicate labels. Once the variable<br />
goes out of scope, we restrict the record with the label to bring the old "variable" into scope.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> <br />
renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') =><br />
Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
One might wonder why we use a Sequence here instead of a list, since we only query the head and prepend. This is implement record merge (.+) more efficiently since we can then use (><) (O(1)) instead of (++) (O(n)) as follows:<br />
<br />
<haskell><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)<br />
(OR l) .++ (OR r) = OR $ M.unionWith (><) l r<br />
</haskell><br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nicer syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57276CTRex2013-12-06T10:09:31Z<p>Atzeus: /* Duplicate labels, and lacks */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>r = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" r </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural''': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
As another use case for duplicate labels: consider implementing an interpreter for some embedded DSL, and you want to carry the <br />
state of the variables in the an extensible record. Declaring a new variable in the embedded language then<br />
causes us to extend the record. Since the embedded language allows shadowing (as most languages do), we can simply<br />
extend the record, we do not have to jump through hoops to make sure there are no duplicate labels. Once the variable<br />
goes out of scope, we restrict the record with the label to bring the old "variable" into scope.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> <br />
renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') =><br />
Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nicer syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57275CTRex2013-12-06T10:06:54Z<p>Atzeus: /* Trex (Hugs) */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>r = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" r </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural''': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
As another use case for duplicate labels: consider implementing an interpreter for some embedded DSL, and you want to carry the <br />
state of the variables in the an extensible record. Declaring a new variable in the embedded language then<br />
causes us to extend the record. Since the embedded language allows shadowing (as most languages do), we can simply<br />
extend the record, we do not have to jump through hoops to make sure there are no duplicate labels. Once the variable<br />
goes out of scope, we remove the label again to bring the old "variable" into scope.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> <br />
renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') =><br />
Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nicer syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57274CTRex2013-12-06T10:06:19Z<p>Atzeus: /* Basic extensible records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>r = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" r </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural''': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
As another use case for duplicate labels: consider implementing an interpreter for some embedded DSL, and you want to carry the <br />
state of the variables in the an extensible record. Declaring a new variable in the embedded language then<br />
causes us to extend the record. Since the embedded language allows shadowing (as most languages do), we can simply<br />
extend the record, we do not have to jump through hoops to make sure there are no duplicate labels. Once the variable<br />
goes out of scope, we remove the label again to bring the old "variable" into scope.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> <br />
renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') =><br />
Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57273CTRex2013-12-06T10:06:00Z<p>Atzeus: /* Basic extensible records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>r = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" r </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
As another use case for duplicate labels: consider implementing an interpreter for some embedded DSL, and you want to carry the <br />
state of the variables in the an extensible record. Declaring a new variable in the embedded language then<br />
causes us to extend the record. Since the embedded language allows shadowing (as most languages do), we can simply<br />
extend the record, we do not have to jump through hoops to make sure there are no duplicate labels. Once the variable<br />
goes out of scope, we remove the label again to bring the old "variable" into scope.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> <br />
renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') =><br />
Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57272CTRex2013-12-06T10:02:07Z<p>Atzeus: /* Duplicate labels, and lacks */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
As another use case for duplicate labels: consider implementing an interpreter for some embedded DSL, and you want to carry the <br />
state of the variables in the an extensible record. Declaring a new variable in the embedded language then<br />
causes us to extend the record. Since the embedded language allows shadowing (as most languages do), we can simply<br />
extend the record, we do not have to jump through hoops to make sure there are no duplicate labels. Once the variable<br />
goes out of scope, we remove the label again to bring the old "variable" into scope.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> <br />
renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') =><br />
Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57271CTRex2013-12-06T09:42:30Z<p>Atzeus: /* Duplicate labels, and lacks */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> <br />
renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') =><br />
Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57270CTRex2013-12-06T09:42:05Z<p>Atzeus: /* Duplicate labels, and lacks */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = (r'.!x, r' .- x)<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> renameUnique :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57269CTRex2013-12-05T16:34:55Z<p>Atzeus: /* HList records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible records] build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57268CTRex2013-12-05T16:30:37Z<p>Atzeus: /* Constrained record operations */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this type class to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57267CTRex2013-12-05T16:30:04Z<p>Atzeus: /* Constrained record operations */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness''' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57266CTRex2013-12-05T16:29:37Z<p>Atzeus: /* Duplicate labels, and lacks */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :++ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness'' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57265CTRex2013-12-05T16:27:18Z<p>Atzeus: /* Operations */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Empty Record:<br />
** Value level: <hask>empty :: Rec Empty </hask><br />
** Type level: <hask> Empty :: Row *</hask><br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :+ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness'' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57264CTRex2013-12-05T16:26:19Z<p>Atzeus: /* Operations */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using the above operations, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :+ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness'' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57263CTRex2013-12-05T16:25:35Z<p>Atzeus: /* Operations */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix, the type level operation is named the same, but starting with a capital. If the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using restriction, selection and selection, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :+ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness'' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57262CTRex2013-12-05T16:24:27Z<p>Atzeus: /* Rows and records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence, we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix the type level operation is named the same, but starting with a capital. And if the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using restriction, selection and selection, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :+ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness'' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57261CTRex2013-12-05T16:24:08Z<p>Atzeus: /* Labels */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix the type level operation is named the same, but starting with a capital. And if the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using restriction, selection and selection, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :+ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness'' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57260CTRex2013-12-05T16:23:28Z<p>Atzeus: /* Difference between Heterogenous maps and extensible records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states, for example, that <hask>x</hask> is associated with <hask>Int</hask>, The label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix the type level operation is named the same, but starting with a capital. And if the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using restriction, selection and selection, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :+ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness'' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57259CTRex2013-12-05T16:22:36Z<p>Atzeus: /* Difference between Heterogenous maps and extensible records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous maps? A hetrogenous map is a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package ).<br />
<br />
In heterogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states for example that <hask>x</hask> is associated with <hask>Int</hask> the label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix the type level operation is named the same, but starting with a capital. And if the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using restriction, selection and selection, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :+ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness'' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57258CTRex2013-12-05T16:21:46Z<p>Atzeus: /* Row polymorphism */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record in which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous map (a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package )).<br />
<br />
In hetrogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states for example that <hask>x</hask> is associated with <hask>Int</hask> the label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix the type level operation is named the same, but starting with a capital. And if the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using restriction, selection and selection, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :+ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness'' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57257CTRex2013-12-05T16:21:10Z<p>Atzeus: /* Row polymorphism */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label <hask>dist</hask> added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous map (a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package )).<br />
<br />
In hetrogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states for example that <hask>x</hask> is associated with <hask>Int</hask> the label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix the type level operation is named the same, but starting with a capital. And if the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using restriction, selection and selection, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :+ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness'' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57256CTRex2013-12-05T16:20:00Z<p>Atzeus: /* Basic extensible records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label dist added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous map (a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package )).<br />
<br />
In hetrogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states for example that <hask>x</hask> is associated with <hask>Int</hask> the label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix the type level operation is named the same, but starting with a capital. And if the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using restriction, selection and selection, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :+ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness'' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57255CTRex2013-12-05T16:19:27Z<p>Atzeus: /* Basic extensible records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell which implements extensible records using closed type families, datakinds and type literals. It does '''not''' use overlapping instances.<br />
<br />
Features:<br />
<br />
* Row-polymorphism<br />
<br />
* Support for scoped labels (i.e. duplicate labels) '''and''' non-scoped labels (i.e. the lacks predicate on rows).<br />
<br />
* The value level interface and the type level interface correspond to each other. <br />
<br />
* The order of labels (except for duplicate labels) does not matter. I.e. {x = 0, y = 0} and {y = 0, x = 0} have the '''same type'''.<br />
<br />
* Syntactic sugar on the value level as well as type level.<br />
<br />
* If all values in a record satisfy a constraint such as <hask>Show</hask>, then we are able to do operations on all fields in a record, if that operation only requires that the constraint is satisfied. In this way we can create instances such as <hask> Forall r Show => Show (Rec r) </hask>. This is available to the application programmer as well.<br />
<br />
* Fast extend, lookup and restriction (all O(log n)) using HashMaps.<br />
<br />
The haddock documentation is available [http://homepages.cwi.nl/~ploeg/openrecdocs/Records.html here].<br />
<br />
= What the hell are extensible records? =<br />
<br />
== Basic extensible records ==<br />
<br />
Records are values that contain other values, which are indexed by name (label). Examples of records are structs in c. In Haskell, we can currently declare record types as follows:<br />
<br />
<haskell> data HRec = HRec { x :: Int, y :: Bool, z :: String } </haskell><br />
<haskell> data HRec2 = HRec2 { p :: Bool, q :: Char } </haskell><br />
<br />
<br />
Extensible records are records where we can add values (with corresponding label) to existing records. Suppose we have a record <hask>x = { x = 0, y = 0 }</hask>. If we have an extensible record system we can then add a value to this record: <br />
<br />
<haskell> extend z "Bla" x </haskell><br />
<br />
Which gives <hask>{x = 0, y = 0 , z = "Bla"} </hask><br />
<br />
A type-level record, i.e. the mapping of labels to types, such as <hask> {x = Int, y = Bool, z = String } </hask>, is called a '''row'''. <br />
<br />
<br />
Such extension is not possible with the <hask>Rec</hask> type above, the fields of the record are fixed. In the non-extensible record system in Haskell currently, the records are typed '''nominally''', which means that we see if two record types are the same by checking the '''names ''' of the records. For example, to check if the type <hask>HRec</hask> is the same as <hask>HRec2</hask>, we check if their names (HRec and HRec2) are equal. n<br />
<br />
In an extensible record system the record type are '''structural'': two records have the same type if they carry the same fields with the same types, i.e. if they have the same row. This also means we do not have to declare the type of the record before using it. For example (in CTRex):<br />
<br />
<haskell> x := 0 .| y := False .| empty </haskell><br />
<br />
Constructs <hask> { x = 0, y = 0 } </hask> with type :<br />
<br />
<haskell> Rec ("x" ::= Int :| y ::= Bool .| Empty) </haskell><br />
<br />
which means <hask> {x = Int, y = Bool } </hask><br />
<br />
This structural typing has the advantage that we can go from <hask> {x = Int, y = Bool } </hask> to <hask> {x = Int, y = Bool, z = String } </hask> by simply adding the field, we do not have to write a specific function to convert the two types (which would have been necessary with nominal record typing).<br />
<br />
Because the associated type for a label is in the row of the record, labels can be used in different records for different types. This is currently not possible with standard records :<br />
<br />
<haskell> <br />
data X = X { x :: Int, y :: Bool } <br />
data Y = Y { x :: Bool } <br />
</haskell><br />
<br />
Will give a Multiple declarations of `x' error.<br />
<br />
To summarize, extensible records have the following advantages:<br />
<br />
* Labels can be used in different records for different types<br />
* Records do not have to be declared before use.<br />
* Structural typing eliminates the need for explicit conversion functions.<br />
<br />
== Row polymorphism ==<br />
<br />
Some extensible record systems, such as CTRex, support '''row polymorphism'''. This concept is best explained by an example, consider the following function (in CTRex):<br />
<br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| Empty) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| Empty)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
<br />
Where <hask>(.!)</hask> is the function to get a value from a record. This function takes a record with row <hask>{ x = Double, y = Double }</hask> and add returns the same record with a label dist added, which carries the distance of the point <hask>(x,y)</hask> from <hask>(0,0)</hask>. <br />
<br />
<br />
Now suppose we want to call this function with record <hask>{ name = "PointA", x = 10, y = 10 }</hask>. This is not possible, because the row <hask>{ name = String, x = Double, y = Double }</hask> and <hask>{ x = Double, y = Double }</hask> are not the same.<br />
<br />
<br />
For this we need row polymorphism: we want to make the function <hask>f</hask> '''polymorphic''' in the rest of the row as follows: <br />
<haskell> <br />
f :: Rec ("x" ::= Double .| "y" ::= Double .| r) <br />
-> Rec ("x" ::= Double .| "y" ::= Double .| "dist" ::= Double .| r)<br />
f r = norm := dist ((r.!x * r.!x) + (r.!y * r.!y)) .| r<br />
</haskell><br />
Notice that in CTRex, the only difference with the previous version is that we replaced <hask>Empty</hask> with <hask>r</hask> in the type. Now the type <hask> f </hask> reads as follows: Given some record where x and y have type Double and there are some other fields with types described by row <hask>r</hask>, return a record which x,y and dist have type Double and there are also some other fields described by row <hask>r</hask>.<br />
<br />
<br />
Of course, since <hask>f</hask> cannot know anything about the the rest of the row, it can do nothing else than just leave it alone or replace it with <hask>undefined</hask>.<br />
<br />
== Difference between Heterogenous maps and extensible records ==<br />
<br />
A question that may arise after the previous section is: What is the difference between extensible records and heterogenous map (a map that can store values of different types, see for example the [http://hackage.haskell.org/package/HMap HMap package] (blatant plug, my package )).<br />
<br />
In hetrogenous maps the type associated with a key is present '''in the type of the key'''. For example, in [http://hackage.haskell.org/package/HMap HMap] a key has type <hask>Key x a</hask> where a is the type of the things we can store at this key, for example <hask>Int</hask>. <br />
<br />
<br />
In extensible records, the type associated with a key (now called label) is stored '''in the type of the record''', i.e. in its row. The row states for example that <hask>x</hask> is associated with <hask>Int</hask> the label itself does not hold any information on the associated type. In fact, the associated type may differ between records. <br />
<br />
<br />
<br />
This means that if a record has <hask>x ::= Int </hask> in its row, then we are '''sure''' that this record has a value of type <hask>Int</hask> for <hask>x</hask>. In a hetrogenous map, we can never be sure if a key is present in a map, i.e. <hask>lookup x m</hask> may return <hask>Maybe</hask>.<br />
<br />
= Programmer interface =<br />
<br />
== Labels ==<br />
<br />
Labels (such as x,y and z) in CTRex are type level symbols (i.e. type level strings). We can point to a label by using the label type:<br />
<br />
<haskell>data Label (s :: Symbol) = Label</haskell><br />
<br />
For example, we can declare shorthands for pointing at the type level symbol "x", "y" and "z" as follows.<br />
<br />
<haskell><br />
x = Label :: Label "x" <br />
y = Label :: Label "y" <br />
z = Label :: Label "z" <br />
</haskell><br />
<br />
Of course it would be much nicer to just write <hask>`x</hask> instead of <hask> Label :: Label "x"</hask> but this is currently not available. This may change in the future.<br />
<br />
In the remainder of this wiki page we write <hask>x</hask> instead of <hask>Label :: Label "x"</hask>, assuming we have already declared <hask>x = Label :: Label "x"</hask><br />
<br />
== Rows and records ==<br />
<br />
<br />
A record has the following type: <hask> Rec (r :: Row *) </hask>, where r is the row. <br />
<br />
The constructors of <hask>Rec</hask> as well as the constructors of the datakind <hask>Row</hask> are not exported. <br />
<br />
Hence we can only manipulate records and rows by the value and type level operations given in the CTRex module.<br />
<br />
== Operations ==<br />
<br />
For all operations available on records, the value level interface and the type level interface correspond to each other. <br />
<br />
For example, the value level operation for extending a record (adding a field) has type <br />
<haskell> extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell> <br />
whereas the type level operation for adding a field has type<br />
<br />
<haskell> Extend :: Symbol -> * -> Row * -> Row * </haskell><br />
<br />
Here we use regular type syntax to denote the kinds of the closed type family <hask>Extend</hask><br />
<br />
In this way each value level operation (that changes the type) has a corresponding type level operation with a similar name. If the value level operation is not infix the type level operation is named the same, but starting with a capital. And if the value level operation is an operator, is starts with a '.' and the type level operation starts with a ':'.<br />
<br />
The following operations are available:<br />
<br />
* Extension:<br />
** Value level: <hask>extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) </hask><br />
** Type level: <hask> Extend :: Symbol -> * -> Row * -> Row *</hask><br />
* Selection:<br />
** Value level: <hask> (.!) :: KnownSymbol l => Rec r -> Label l -> r :! l </hask><br />
** Type level: <hask> (:!) :: Row * -> Symbol -> * </hask><br />
* Restriction:<br />
**Value level: <hask>(.-) :: KnownSymbol l => Rec r -> Label l -> Rec (r :- l)</hask><br />
** Type level: <hask> (:-) Row * -> Symbol -> Row * </hask><br />
* Record merge :<br />
** Value level: <hask><br />
(.++) :: Rec l -> Rec r -> Rec (l :++ r)</hask><br />
** Type level: <hask> (:++) :: Row * -> Row * -> Row *</hask><br />
<br />
* Rename (This operation can also be expressed using restriction, selection and selection, but this looks nicer):<br />
** Value level: <hask>rename :: (KnownSymbol l, KnownSymbol l') => Label l -> Label l' -> Rec r -> Rec (Rename l l' r) </hask><br />
** Type level: <hask>Rename :: Symbol -> Symbol -> Row * -> Row * </hask><br />
<br />
== Syntactic Sugar ==<br />
We provide some handy declarations which allow us to chain operations with nicer syntax. For example we can write:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
instead of <br />
<br />
<haskell> <br />
rename z p $ update y 'b' $ extendUnique z False $ extend x 2 $ extend y 'a' empty<br />
</haskell><br />
<br />
For this we have a GADT datatype RecOp which takes two arguments: <br />
<br />
* c, the type of the constaint that should hold on the input row.<br />
* rop, the row operation (see below). with the following constructors:<br />
<br />
This datatype has the following constructors, all of which are sugar for record operations.<br />
<br />
* <hask>(:<-) :: Label -> a -> RecOp (HasType l a) RUp</hask> Record update. Sugar for update.<br />
* <hask>(:=) :: KnownSymbol l => Label l -> a -> RecOp NoConstr (l ::= a)</hask> Record extension. Sugar for extend.<br />
* <hask>(:!=) :: KnownSymbol l => Label l -> a -> RecOp (Lacks l) (l ::= a)</hask> Record extension, without shadowing. Sugar for extendUnique. See the section on duplicate labels.<br />
* <hask>(:<-|) :: (KnownSymbol l, KnownSymbol l') => Label l' -> Label l -> RecOp NoConstr (l' ::<-| l)</hask> Record label renaming. Sugar for rename.<br />
* <hask>(:<-!) :: (KnownSymbol l, KnownSymbol l', r :\ l') => Label l' -> Label l -> RecOp (Lacks l') (l' ::<-| l)</hask> Record label renaming. Sugar for renameUnique. See the section on duplicate labels.<br />
<br />
<br />
On the type level the same pattern again arises, we have a datakind (RowOp *) with the following constructors:<br />
<br />
* <hask>RUp :: RowOp * </hask> Row operation for<hask>(:<-)</hask>. Identitity row operation.<br />
* <hask>(::=) :: Symbol -> * -> RowOp * </hask> Row extension operation. Sugar for Extend. Type level operation for <hask>(:=)</hask> and <hask>(:!=)</hask><br />
* <hask>::<-|</hask> Row renaming. Sugar for Rename. Type level operation for <hask>(:<-|)</hask> and <hask>(:<-!)</hask><br />
<br />
We then have a type level operation to perform a row operation:<br />
<br />
<haskell> (:|) :: RowOp * -> Row * -> Row * </haskell><br />
<br />
And a value level operation to perform a record operation:<br />
<br />
<haskell> (.|) :: c r => RecOp c ro -> Rec r -> Rec (ro :| r) </haskell><br />
<br />
Notice that the constraint from the record operation is placed on the input row.<br />
<br />
Also notice that this means that this sugar is also available when writing types:<br />
<br />
<haskell> Rec ("p" ::<-| "z" :| RUp :| "z" ::= Bool :| "x" ::= Double :| "y" ::= Char :| Empty) </haskell> <br />
<br />
is the type exactly corresponding to:<br />
<haskell> <br />
p :<-| z .| y :<- 'b' .| z :!= False .| x := 2 .| y := 'a' .| empty <br />
</haskell> <br />
<br />
and equivalent to <br />
<haskell><br />
Rename "p" "z" (Extend "z" Bool (Extend x Double (Extend "x" Int Empty)))<br />
</haskell><br />
<br />
and of course equivalent to:<br />
<br />
<haskell><br />
"p" ::= Bool :| "x" ::= Double :| "y" ::= Int :| Empty<br />
</haskell><br />
<br />
== Duplicate labels, and lacks ==<br />
<br />
Rows and records can contain duplicate labels as described in the paper [http://legacy.cs.uu.nl/daan/download/papers/scopedlabels.pdf Extensible records with scoped labels] by Daan Leijen. <br />
<br />
Hence we can write:<br />
<br />
<haskell> z = x := 10 .| x := "bla" .| Empty :: Rec ("x" ::= Int :| "x" ::= String :| Empty)<br />
</haskell><br />
<br />
We can recover the information on the second instance of x by removing x:<br />
<haskell> z .- x :: Rec ("x" ::= String :| Empty) </haskell><br />
<br />
The motivation for this is as follows: Suppose we have a function <br />
<haskell> f :: Rec ("x" ::= Int :| r) -> (Rec ("x" ::= Bool .| r) </haskell><br />
<br />
and we want to write the following function:<br />
<br />
<haskell><br />
g :: Rec r -> Rec ("p" ::= String .| r)<br />
g r = let r' = f (x := 10 .| r)<br />
(c,r'') = decomp r x<br />
v = if c then "Yes" else "Nope"<br />
in p := v .| r''<br />
</haskell><br />
<br />
If it was not possible for records and rows to contain duplicate label the type of g would be:<br />
<br />
<haskell> <br />
g :: r :\ "x" => Rec r -> Rec ("p" ::= String .| r) <br />
</haskell><br />
<br />
The constraint <hask> r :\ "x" </hask> reads as r lacks "x". The constraint leaks the implementation of g. We only use "x" internally in g , there is no reason for this constraint to hold in the rest of the program.<br />
<br />
However, in other situations, duplicate labels may be undesired, for instance because we want to be sure that we do not hide previous information. For this reason we also provide the already introduced `lacks` constraint.<br />
<br />
We also provide a handy record extension function that has this constraint, so that you do not have to add types yourself:<br />
<br />
<haskell> extendUnique :: (KnownSymbol l, l :\ r) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
The same thing for renaming:<br />
<haskell> extendUnique :: (KnownSymbol l, r :\ l) => Label l -> a -> Rec r -> Rec (Extend l a r) </haskell><br />
<br />
We also provide a constraint to test that two Rows are '''disjoint'''. Corresponding to this we also provide a function to merge with this constraint:<br />
<br />
<haskell> .+ :: Disjoint l r => Rec l -> Rec r -> Rec (l :+ r) </haskell><br />
<br />
Notice that .+ is commutative, while .++ is not.<br />
<br />
== Constrained record operations ==<br />
<br />
If some constraint c holds for all types in the row of a record, then the methods in the following type class are available:<br />
<haskell> <br />
class Forall (r :: Row *) (c :: * -> Constraint) where<br />
rinit :: CWit c -> (forall a. c a => a) -> Rec r<br />
erase :: CWit c -> (forall a. c a => a -> b) -> Rec r -> [(String,b)]<br />
eraseZip :: CWit c -> (forall a. c a => a -> a -> b) -> Rec r -> Rec r -> [(String,b)]<br />
</haskell><br />
<br />
<br />
Here <hask> CWit </hask> is a datatype that serves as a '''witness'' of a constraint. It's definition is as follows:<br />
<br />
<haskell> data CWit (c :: * -> Constraint) = CWit </haskell><br />
<br />
We can use this to specify the constraint which should hold on the row, since this may be ambiguous. We can use this to implement, for example, some standard type classes on records:<br />
<br />
<br />
<haskell><br />
instance (Forall r Show) => Show (Rec r) where<br />
show r = "{ " ++ meat ++ " }"<br />
where meat = intercalate ", " binds<br />
binds = map (\(x,y) -> x ++ "=" ++ y) vs<br />
vs = erase (CWit :: CWit Show) show r<br />
<br />
instance (Forall r Eq) => Eq (Rec r) where<br />
r == r' = and $ map snd $ eraseZip (CWit :: CWit Eq) (==) r r'<br />
<br />
instance (Forall r Bounded) => Bounded (Rec r) where<br />
minBound = rinit (CWit :: CWit Bounded) minBound<br />
maxBound = rinit (CWit :: CWit Bounded) maxBound<br />
</haskell><br />
<br />
<br />
We could make an interface to do even more general stuff on (pairs of) constrained records, but I have yet to find a use case for this.<br />
<br />
== Type errors ==<br />
<br />
Here we list which type errors are reported when using CTRex:<br />
<br />
* Record does not have field <br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y + 1 </haskell><br />
<br />
<haskell><br />
No instance for (Num (Records.NoSuchField "y"))<br />
arising from a use of ‛+’<br />
In the expression: (x := 1 .| empty) .! y + 1<br />
</haskell><br />
<br />
Somewhat unsatisfactory, the expression:<br />
<br />
<haskell> typerr1 = (x := 1 .| empty) .! y </haskell><br />
<br />
does not immediatly give a type error. Instead its type is:<br />
<br />
<haskell> typerr1 :: Records.NoSuchField "y" </haskell><br />
<br />
* Record does not lack field<br />
<br />
<haskell> x :!= 1 .| x := 1 .| empty </haskell><br />
<br />
Gives the error<br />
<br />
<haskell><br />
Error:<br />
Couldn't match type ‛'Records.LabelNotUnique "x"’<br />
with ‛'Records.LabelUnique "x"’<br />
In the first argument of ‛(.|)’, namely ‛x :!= 1’<br />
</haskell><br />
<br />
* Records not disjoint <br />
<br />
<haskell><br />
notdisjoint = let p = x := 2 .| empty<br />
q = x := 2 .| empty<br />
in p .+ q<br />
</haskell><br />
<br />
Gives the error:<br />
<br />
<haskell><br />
Couldn't match type ‛'Records.Duplicate "x"’<br />
with ‛'Records.IsDisjoint’<br />
Expected type: 'Records.IsDisjoint<br />
Actual type: Records.DisjointR<br />
('Records.R '["x" 'Records.:-> a4])<br />
('Records.R '["x" 'Records.:-> a])<br />
In the expression: p .+ q<br />
</haskell><br />
<br />
= Implementation =<br />
<br />
The basis of the implementation is a label-type pair, which is represented by the following (unexported) datakind:<br />
<br />
<haskell> data LT a = Symbol :-> a </haskell><br />
<br />
== Rows ==<br />
<br />
A row is then simply a list of such label-type pairs:<br />
<haskell> newtype Row a = R [LT a] -- constructor not exported </haskell><br />
<br />
Notice that the constructor is not exported, so if we ask for the type of <br />
<br />
<haskell> <br />
origin2 = y := 0 .| x := 0 .| empty<br />
</haskell><br />
<br />
we get: <br />
<haskell><br />
origin2<br />
:: Rec ('Records.R '["x" 'Records.:-> Double, "y" 'Records.:-> Double])<br />
</haskell><br />
<br />
Here the implementation of Row leaks a bit. The user cannot write down this type, since Records.R is not exported, and neither is :->.<br />
<br />
Instead the user should not worry about the implementation and write the type in terms of operations (or let the type be inferred), i.e. :<br />
<br />
<haskell><br />
origin2 :: Rec ("x" ::= Double :| "y" ::= Double :| Empty)<br />
</haskell><br />
<br />
Operations on rows are implemented using closed type families. The list of label-type pairs in the row are always sorted. Each operation defined on Rows maintains this invariant. We are sure that no operations which violate this invariant can be created by the user, since the constructor is not exported. <br />
<br />
To keep the list of label-type pairs sorted, we use the built-in closed type family :<br />
<haskell> <.=? :: Symbol -> Symbol -> Bool </haskell><br />
Which compares two symbols at compile time and gives a Bool datakind telling us wether the left is <= than the right.<br />
<br />
For instance, row extension is implemented as follows:<br />
<br />
<haskell><br />
type family Extend :: Symbol -> * -> Row * -> Row * where<br />
Extend l a (R x) = R (Inject (l :-> a) x)<br />
<br />
type family Inject :: LT * -> [LT *] -> [LT *] where<br />
Inject (l :-> t) '[] = (l :-> t ': '[])<br />
Inject (l :-> t) (l' :-> t' ': x) = <br />
Ifte (l <=.? l')<br />
(l :-> t ': l' :-> t' ': x)<br />
(l' :-> t' ': Inject (l :-> t) x)<br />
</haskell><br />
<br />
== Records ==<br />
<br />
To implement the records we introduce the following datatype which can contain anything:<br />
<br />
<haskell><br />
data HideType where<br />
HideType :: a -> HideType<br />
</haskell><br />
<br />
A record is then defined as follows:<br />
<br />
<haskell><br />
-- | A record with row r.<br />
data Rec (r :: Row *) where<br />
OR :: HashMap String (Seq HideType) -> Rec r<br />
</haskell><br />
<br />
Here we see that a record is actually just a map from string to the sequence of values. Notice that it is a sequence of values and not a single value, because the record may contain duplicate labels. <br />
<br />
Extension is then rather simple, it simply prepends the value to the list of values associated with the label:<br />
<br />
<haskell><br />
extend :: KnownSymbol l => Label l -> a -> Rec r -> Rec (Extend l a r) <br />
extend (show -> l) a (OR m) = OR $ M.insert l v m <br />
where v = HideType a <| M.lookupDefault S.empty l m<br />
</haskell><br />
<br />
To safely convert back from Hidetype, we maintain the following invariant: <br />
The i-th value in the sequence associated with "x" has the i-th type associated with "x" in the row. This invariant is maintained by all operations on rows and records. Since the constructors of record and row are not exported, we know that it is impossible to declare new operations on records and rows that violate this invariant. A same kind of trick is used [http://hackage.haskell.org/package/HMap HMap].<br />
<br />
Since we know that the actual type of the value in Hidetype is given in the row, we can safely convert it back:<br />
<haskell><br />
(.!) :: KnownSymbol l => Rec r -> Label l -> r :! l<br />
(OR m) .! (show -> a) = x'<br />
where x S.:< t = S.viewl $ m M.! a <br />
-- notice that this is safe because of invariant<br />
x' = case x of HideType p -> unsafeCoerce p <br />
</haskell><br />
<br />
The use of <hask>unsafeCoerce</hask> here cannot go wrong because of the invariant.<br />
<br />
= Comparison with other approaches =<br />
<br />
Here we compare with other approaches. <br />
<br />
== HList records ==<br />
<br />
The [http://hackage.haskell.org/package/HList HList package] provides [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-Record.html extensible] records build on heterogeneous lists. The differences with are as follows:<br />
<br />
* Rows are not sorted. This has the following downsides:<br />
** Compile time operations, such as reordering the labels and checking for duplicate labels is costly (at compile time) [http://code.haskell.org/~aavogt/HList-benchmark/a.html link]<br />
** <hask>{x = 0, y = 0 }</hask> and <hask>{ y = 0, x = 0 } </hask> do not have the same type. Hence, is we give a type declaration for the first and want to put in the latter, we need to call a manual conversion function.<br />
* Constrained row operations are less convenient, since <br />
** the Record newtype needs to be unwrapped, and<br />
** there is no definition provided to create instances of [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-FakePrelude.html#t:ApplyAB applyAB] that work on <hask>LVPair l a</hask> from instances that work on just <hask>a</hask><br />
** however there similarities between the two<br />
{|<br />
! CTRex function<br />
! HList function(s)<br />
|-<br />
| rinit<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#g:15 hReplicate]<br />
|-<br />
| erase<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HList.html#t:HMapOut hMapOut]<br />
|-<br />
| eraseZip<br />
| [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-HZip.html hZip] with hMapOut<br />
|}<br />
* Currently, the run time complexity of extend, restriction and lookup is O(n), versus O(log n) for CTRex. Proposals have been done to make this faster [http://www.fing.edu.uy/~mviera/papers/pepm13.pdf paper].<br />
* Duplicate labels are disallowed in HList.<br />
* HList has nicer support for [http://hackage.haskell.org/package/HList-0.3.0.1/docs/Data-HList-MakeLabels.html writing labels]<br />
<br />
== Trex (Hugs) ==<br />
<br />
The Haskell interpreter hugs supports a type system extension that allows extensible records. The differences are as follows:<br />
<br />
* Constrained row operations are not available(?)<br />
* Duplicate labels are disallowed<br />
* Record merge not available (?)<br />
* Can pattern match on records(?)<br />
* Nice syntax<br />
* More (?)<br />
<br />
== More ==<br />
<br />
More to come! Please add stuff!<br />
<br />
= Wishlist =<br />
<br />
* Nicer syntax for Label :: Label "x"<br />
* Nice syntax for records, i.e. { x = 0, y = 0 }.<br />
* Type level Show thing, so that we can pretty-print types, and the implementation of things such as row does not leak.<br />
* The ability to pattern match on records.<br />
* Type level <hask>error</hask> so that we can generate clearer error messages earlier.</div>Atzeushttps://wiki.haskell.org/index.php?title=CTRex&diff=57254CTRex2013-12-05T16:18:25Z<p>Atzeus: /* Basic extensible records */</p>
<hr />
<div>= Introduction =<br />
<br />
This page describes the design, usage and motivation for [https://github.com/atzeus/CTRex CTRex].<br />
<br />
CTRex is a library for Haskell