|
1 | | -module Data.Chronofold where |
2 | | - |
3 | | --- import Control.Monad.Update |
4 | | --- import Data.Monoid.Action |
5 | | - |
6 | | - |
7 | | -import Data.Array (foldl, insertAt, length, snoc) |
8 | | -import Data.Array as Array |
9 | | -import Data.Bounded (bottom) |
10 | | -import Data.Enum (fromEnum) |
11 | | - |
12 | | -import Data.Generic.Rep (class Generic) |
13 | | -import Data.Generic.Rep.Eq (genericEq) |
14 | | -import Data.Generic.Rep.Show (genericShow) |
15 | | -import Data.Map (Map, empty, insert, member) |
16 | | -import Data.Maybe (Maybe(..)) |
17 | | -import Data.String (CodePoint, singleton, splitAt, toCodePointArray) |
18 | | -import Data.String (length) as S |
19 | | - |
20 | | -import Effect.Exception (error) |
21 | | -import Effect.Exception.Unsafe (unsafeThrowException) |
22 | | -import Prelude (class Eq, class Ord, class Show, compare, eq, show, ($), (&&), (+), (-), (<>), (==)) |
23 | | - |
24 | | - |
25 | | --- processes α β γ in the paper, for me replica is better than process/site/author |
26 | | --- | |
27 | | -newtype Replica = Replica Int |
28 | | -derive newtype instance showReplica :: Show Replica |
29 | | -derive newtype instance ordReplica :: Ord Replica |
30 | | -derive newtype instance eqReplica :: Eq Replica |
31 | | - |
32 | | -type ReplicaIndex = Int |
33 | | --- derive newtype instance showReplicaIndex :: Show ReplicaIndex |
34 | | - |
35 | | --- type Timestamp = Replica /\ ReplicaIndex |
36 | | -data Timestamp = Timestamp Replica ReplicaIndex |
37 | | -instance showTimestamp :: Show Timestamp where |
38 | | - show (Timestamp (Replica 1) i) = "α" <> show i |
39 | | - show (Timestamp (Replica 2) i) = "β" <> show i |
40 | | - show (Timestamp (Replica 3) i) = "γ" <> show i |
41 | | - show (Timestamp (Replica r) i) = "rep" <> show r <> "/" <> show i |
42 | | -instance eqTimestamp :: Eq Timestamp where |
43 | | - eq (Timestamp a i) (Timestamp b j) = eq a b && eq i j |
44 | | -instance ordTimestamp :: Ord Timestamp where |
45 | | - compare (Timestamp a i) (Timestamp b j) = (compare a b) <> (compare i j) |
46 | | - |
47 | | --- from the paper |
48 | | --- auth :: Timestamp -> Replica |
49 | | --- auth = snd |
50 | | - |
51 | | --- andx :: Timestamp -> ReplicaIndex |
52 | | --- andx = fst |
53 | | - |
54 | | --- newtype Value = Value CodePoint |
55 | | --- derive instance newTypeValue :: Newtype Value _ |
56 | | --- derive newtype instance showValue :: Show Value |
57 | | - |
58 | | ------------------------------------------- |
59 | | ------------------------------------------- |
60 | | ------------------------------------------- |
61 | | - |
62 | | - |
63 | | --- τ 0 1 2 3 |
64 | | --- α : α6 |
65 | | --- (6α) |
66 | | --- \ |
67 | | --- β : α6 |
68 | | --- tβ (6β) |
69 | | - |
70 | | --- log of indices is the local coordinate system |
71 | | --- log of timestamp is the shared coordinate system |
72 | | - |
73 | | --- |
74 | | --- chronofold :: ndx -> <val,nxt> |
75 | | --- ndx 1α 2α 3α 4α 5α 6α |
76 | | --- val 0 P I N S K |
77 | | - |
78 | | --- Maybe we want a Natural index or an Array parameterised by it's index type |
79 | | --- type Chronofold = Array ({ codepoint :: Value, next :: Int }) |
80 | | --- in practice we use a thinned chronofold and offload the linked list management to a separate co-structure |
81 | | -type Chronofold = Array CodePoint |
82 | | -data Index = Index Int | Infinity |
83 | | -instance showIndex :: Show Index where |
84 | | - show Infinity = "∞" |
85 | | - show (Index i) = "Index " <> show i |
86 | | -instance eqIndex :: Eq Index where |
87 | | - eq (Index i) (Index j) = i == j |
88 | | - eq (Infinity) (Infinity) = true |
89 | | - eq _ _ = false |
90 | | - |
91 | | --- nxt 2α 3α 4α 5α 6α ⊤ |
92 | | - |
93 | | --- This could be a sparse array (Offset map in the Rust implementation https://github.com/dkellner/chronofold/blob/main/src/lib.rs#L174) |
94 | | -type Next = Array Index |
95 | | - |
96 | | --- co-structures |
97 | | --- ndx :: t -> ndx |
98 | | --- t α1 α2 α3 α4 α5 α6 β1 |
99 | | --- ndx 1α 2α 3α 4α 5α 6α 7α |
100 | | - |
101 | | --- Map Timestamp Index |
102 | | -type Ndx = Map Timestamp Index -- (α1, 1) |
103 | | --- type Ndx = Map Author (Array Index) -- (α, [1]) |
104 | | - |
105 | | --- ndx⁻¹ :: ndx -> t |
106 | | --- ndx 1α 2α 3α 4α 5α 6α 7 |
107 | | --- t α1 α2 α3 α4 α5 α6 β1 |
108 | | - |
109 | | -type NdxInv = Array Timestamp |
110 | | - |
111 | | --- ref :: timestamp -> timestamp |
112 | | --- t α1 α2 α3 α4 α5 α6 |
113 | | --- ref(t) 0 α1 α2 α3 α4 α5 |
114 | | - |
115 | | --- ref is conceptually a function from timestamp to timestamp |
116 | | --- Map Timestamp Timestamp |
117 | | -type Ref = Map Timestamp (Maybe Timestamp) |
118 | | - |
119 | | --- op |
120 | | --- t α2 |
121 | | --- ref α1 |
122 | | --- val 0 |
123 | | - |
124 | | --- An op is what transmitted across replicas and is uniquely identified by it's timestamp t = <i, β> . |
125 | | --- An op is a tuple ⟨t,ref(t), val(t)⟩ |
126 | | --- op |
127 | | --- t α2 -- uniquely identified by it's timestamp t = <i, β> |
128 | | --- ref α1 |
129 | | --- val P |
130 | | - |
131 | | --- t ref val |
132 | | -data Op = Op Timestamp (Maybe (Timestamp)) CodePoint |
133 | | -derive instance genericOp :: Generic Op _ |
134 | | -instance showOp :: Show Op where show = genericShow |
135 | | - |
136 | | --- thinned Chonofold |
137 | | --- | |
138 | | --- current timestamp | weave causal tree |
139 | | --- | | | | |
140 | | --- v v v v |
141 | | -data Log = Log Timestamp Chronofold Next Ndx NdxInv Ref |
142 | | -derive instance genericLog :: Generic Log _ |
143 | | -instance showLog :: Show Log where show = genericShow |
144 | | -instance eqLog :: Eq Log where eq = genericEq |
145 | | - |
146 | | --- instance showLog :: Show Log where |
147 | | --- show (Log l) = show l |
148 | | - |
149 | | --- class Monoid w <= Action w s where |
150 | | --- act :: w -> s -> s |
151 | | - |
152 | | --- instance actionLog :: Action Op Log where |
153 | | --- act o l = appendOp l o |
154 | | - |
155 | | --- class (Action w s, Monad m) <= MonadUpdate m w s | m -> s , m -> w |
156 | | --- instance monadUpdateLog :: MonadUpdate (UpdateState Op Log) Op Log where |
157 | | --- -- putAction :: w -> m Unit |
158 | | --- putAction :: Op -> UpdateState Unit |
159 | | --- putAction op = pure unit |
160 | | --- -- getState :: m s |
161 | | --- getState :: UpdateState Log |
162 | | --- getState = pure $ Log { replica : Replica 1, ops : []} |
163 | | - |
164 | | - |
165 | | --- data Log = Log ReplicaIndex Chronofold Next NextInv Ref |
166 | | --- Op t ref val |
167 | | --- | Appends an `Op` |
168 | | --- | ```purescript |
169 | | --- | >>> logShow $ appendOp (emptyLog (Replica 1)) (Op (Timestamp alpha 2) (Just (Timestamp alpha 1)) (codePointFromChar 'P')) |
170 | | --- | (Log α1 [(CodePoint 0x50)] [1,∞] (fromFoldable [(Tuple α1 0),(Tuple α2 1)]) [α1,α2] (fromFoldable [(Tuple α1 Nothing),(Tuple α2 (Just α1))])) |
171 | | --- | ``` |
172 | | - |
173 | | --- data Log = Log ReplicaIndex Chronofold Next NextInv Ref |
174 | | --- emptyLog :: Replica -> Log |
175 | | --- emptyLog rep = Log |
176 | | --- (Timestamp rep 0) |
177 | | --- [] |
178 | | --- [] |
179 | | --- empty |
180 | | --- [] |
181 | | --- empty |
182 | | - |
183 | | --- root :: Replica -> Op |
184 | | --- root rep = Op (Timestamp rep 1) Nothing bottom |
185 | | - |
186 | | -appendOp :: Log -> Op -> Log |
187 | | -appendOp (Log (Timestamp r i) c next ndxM ndxinv ref) (Op t@(Timestamp r' i') oref v) = |
188 | | - let |
189 | | - cur = length c -- current local index |
190 | | - newNext = case oref of |
191 | | - Nothing -> [Infinity] |
192 | | - _ -> case insertAt (cur - 1) (Index $ cur) next of |
193 | | - Just a -> a |
194 | | - Nothing -> unsafeThrowException (error "index out of bounds in next") |
195 | | - newNdxM = case member t ndxM of |
196 | | - true -> unsafeThrowException (error "didn't expect the timestamp to already exist in ndx") |
197 | | - false -> insert t (Index $ cur + 1) ndxM |
198 | | - newRef = case member t ref of |
199 | | - true -> unsafeThrowException (error "didn't expect the timestamp to already exist in ref") |
200 | | - false -> insert t oref ref |
201 | | - newNdxInv = case (insertAt cur t ndxinv) of |
202 | | - Just a -> a |
203 | | - Nothing -> unsafeThrowException (error "index out of bounds in ndx⁻¹") |
204 | | - in |
205 | | - Log |
206 | | - (Timestamp r (i + 1)) |
207 | | - (snoc c v) |
208 | | - newNext |
209 | | - newNdxM |
210 | | - newNdxInv |
211 | | - newRef |
212 | | - |
213 | | --- | |
214 | | --- | Appends an `Op` without checking invariants. |
215 | | --- | |
216 | | --- unsafeAppendOp :: Log -> Op -> Log |
217 | | --- unsafeAppendOp |
218 | | - |
219 | | -appendOps :: Log -> Array Op -> Log |
220 | | -appendOps = Array.foldl appendOp |
221 | | - |
222 | | --- buildOp needs a cursor position |
223 | | -buildOp :: Log -> CodePoint -> Op |
224 | | -buildOp log@(Log (Timestamp rep i) a b c d e) val = |
225 | | - Op (Timestamp rep (i + 1)) (Just (Timestamp rep (i))) val |
226 | | - |
227 | | -appendString :: Log -> String -> Log |
228 | | -appendString l s = |
229 | | - let chars = toCodePointArray s |
230 | | - in foldl (\l' c -> appendOp l' $ buildOp l' c) l chars |
231 | | - |
232 | | --- type Chronofold = Array Value |
233 | | - |
234 | | --- data Log = Log ReplicaIndex Chronofold Next NextInv Ref |
235 | | --- emptyLog :: Replica -> Log |
236 | | --- emptyLog rep = Log |
237 | | --- (Timestamp rep 1) |
238 | | --- [] |
239 | | --- [Infinity] |
240 | | --- (insert (Timestamp rep 1) (Index 0) empty) |
241 | | --- [(Timestamp rep 1)] |
242 | | --- (insert (Timestamp rep 1) Nothing empty) |
243 | | - |
244 | | -emptyLog :: Replica -> Log |
245 | | -emptyLog rep = Log |
246 | | - (Timestamp rep 0) |
247 | | - [] |
248 | | - [] |
249 | | - empty |
250 | | - [] |
251 | | - empty |
252 | | - |
253 | | -root :: Replica -> Op |
254 | | -root rep = Op (Timestamp rep 1) Nothing bottom |
255 | | - |
256 | | --- interpret the log into a String. |
257 | | --- project :: Log -> GPU TypedArray |
258 | | --- diffable project (incremental). Array Op -> Mutation TypedArray |
259 | | - |
260 | | -naiveProject :: Log -> String |
261 | | -naiveProject (Log _ cps _ _ _ _) = foldl go "" cps |
262 | | - where |
263 | | - go :: String -> CodePoint -> String |
264 | | - go b a = case fromEnum a of |
265 | | - 0x0000 -> b |
266 | | - 0x0008 -> (splitAt ((S.length b) - 1) b).before |
267 | | - _ -> b <> singleton a |
268 | | - |
| 1 | +module Data.Chronofold |
| 2 | + ( module Data.Chronofold.Core |
| 3 | + , module Data.Chronofold.Buffer) where |
| 4 | + |
| 5 | +import Data.Chronofold.Core |
| 6 | +import Data.Chronofold.Buffer |
| 7 | + |
| 8 | +-- | Conceptually we have: |
| 9 | +-- | - a "shared" reference/space, in which |
| 10 | +-- | we're aware of all replicas. `Op`s exist in this space and |
| 11 | +-- | use replica indexed references (Log timestamps). |
| 12 | +-- | - a "local" space, in each replica (Log indices) |
| 13 | +-- | - a "local screen/buffer" space, where Chronofolds are "rendered" |
| 14 | +-- | and which eventually converge by construction. |
0 commit comments