forked from VinylRecords/Vinyl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.lhs
105 lines (72 loc) · 3.38 KB
/
README.lhs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
This README is Literate Haskell; to play, run the following:
ghci README.lhs
GHC 7.6 offers type level kinds for strings and natural numbers; the
former allows a general solution to the records-problem, featuring
static structural typing (with a subtyping relation), and automatic
row-polymorphic lenses. All this is possible without Template Haskell.
Let's work through a quick example. We'll need to enable some language
extensions first:
> {-# LANGUAGE DataKinds, TypeOperators, NoMonomorphismRestriction #-}
> {-# LANGUAGE OverlappingInstances, FlexibleInstances, FlexibleContexts #-}
> import Data.Records
> import Control.Category
> import Prelude hiding (id, (.))
Let's define the fields we want to use:
> name = Field :: "name" ::: String
> age = Field :: "age" ::: Int
> sleeping = Field :: "sleeping" ::: Bool
Now, let's try to make an entity that represents a man:
> jon = (name, "jon")
> :& (age, 20)
> :& (sleeping, False)
> :& RNil
We could make an alias for the sort of entity that `jon` is:
> type LifeForm = Rec '["name" ::: String, "age" ::: Int, "sleeping" ::: Bool]
> jon :: LifeForm
The types are inferred, though, so this is unnecessary unless you'd like
to reuse the type later. Now, make a dog! Dogs are life-forms, but
unlike men, they have masters. So, let's build my dog:
> master = Field :: "master" ::: LifeForm
> tucker = (name, "tucker")
> :& (age, 7)
> :& (sleeping, True)
> :& (master, jon)
> :& RNil
Using Lenses
------------
Now, if we want to wake entities up, we don't want to have to write a
separate wake-up function for both dogs and men (even though they are of
different type). Luckily, we can use the built-in lenses to focus on a
particular field in the record for access and update, without losing
additional information:
> wakeUp :: IElem ("sleeping" ::: Bool) fields => Rec fields -> Rec fields
> wakeUp = rPut sleeping False
Now, the type annotation on `wakeUp` was not necessary; I just wanted to
show how intuitive the type is. Basically, it takes as an input any
record that has a `BOOL` field labelled `sleeping`, and the modifies
that specific field in the record accordingly.
> tucker' = wakeUp tucker
> jon' = wakeUp jon'
We can also access the entire lens for a field using the `rLens`
function; since lenses are composable, it's super easy to do deep update
on a record:
> masterSleeping :: IElem ("master" ::: LifeForm) fields => Lens (Rec fields) Bool
> masterSleeping = rLens sleeping . rLens master
> tucker'' = put masterSleeping True tucker'
(Again, the type annotation is unnecessary.)
Subtyping Relation and Coercion
-------------------------------
A record `Rec xs` is a subtype of a record `Rec ys` if `ys` is a subset
of `xs`; that is to say, if one record can do everything that another
record can, the former is a subtype of the latter. As such, we should be
able to provide an upcast operator which "forgets" whatever makes one
record different from another (whether it be extra data, or different
order).
Therefore, the following works:
> upcastedTucker :: LifeForm
> upcastedTucker = cast tucker
The subtyping relationship between record types is expressed with the
`(<:)` constraint; so, `cast` is of the following type:
< cast :: ss <: ts => Rc ss -> Rec ts
Also provided is a (:~:) constraint which indicates record congruence
(that is, two record types differ only in the order of their fields).