Skip to content

Commit 863311b

Browse files
committed
Basic log operations and projection to text
1 parent 3b9e99f commit 863311b

File tree

5 files changed

+436
-311
lines changed

5 files changed

+436
-311
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ TBD
2323

2424
## Documentation
2525

26-
TBD
26+
<!-- Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-chronofold). -->

src/Data/Chronofold.purs

Lines changed: 14 additions & 268 deletions
Original file line numberDiff line numberDiff line change
@@ -1,268 +1,14 @@
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.

src/Data/Chronofold/Buffer.purs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
module Data.Chronofold.Buffer where
2+
3+
import Control.MonadZero (guard)
4+
import Data.Array (foldl, (!!))
5+
import Data.Chronofold.Core (Index(..), Log(..), appendOp, buildSnocOp)
6+
import Data.Enum (fromEnum)
7+
import Data.FoldableWithIndex (foldlWithIndex)
8+
import Data.Maybe (Maybe(..), maybe)
9+
import Data.String (CodePoint, splitAt, toCodePointArray)
10+
import Data.String as S
11+
import Data.String.CodePoints (singleton)
12+
import Prelude (($), (-), (<>), (==), discard, bind, pure, identity)
13+
14+
-- |
15+
-- | Build an `Op` which inserts the `CodePoint` at the given position.
16+
-- |
17+
-- buildInsertOp :: Log -> Int -> CodePoint -> Op
18+
-- buildInsertOp log@(Log (Timestamp rep i) a b c d e) val =
19+
-- Op (Timestamp rep (i + 1)) (Just (Timestamp rep (i))) val
20+
21+
appendString :: Log -> String -> Log
22+
appendString l s =
23+
let chars = toCodePointArray s
24+
in foldl (\l' c -> appendOp l' $ buildSnocOp l' c) l chars
25+
26+
-- interpret the log into a String.
27+
-- project :: Log -> GPU TypedArray
28+
-- diffable project (incremental). Array Op -> Mutation TypedArray
29+
30+
naiveProject :: Log -> String
31+
naiveProject (Log _ cps next _ _ _) = foldl go "" cps
32+
where
33+
go :: String -> CodePoint -> String
34+
go b a = case fromEnum a of
35+
0x0000 -> b
36+
0x0008 -> (splitAt ((S.length b) - 1) b).before
37+
_ -> b <> singleton a
38+
39+
-- Trying to write this with a fold with the relinking
40+
-- doesn't work because we can't skip ahead or back. Seems like
41+
-- a job perfectly suited for a comonad. We'll do that next.
42+
project :: Log -> String
43+
project (Log _ codepoints next _ _ _) = go 0 "" codepoints next
44+
where
45+
go :: Int -> String -> Array CodePoint -> Array Index -> String
46+
go i acc cps ndxs = maybe acc identity $ do
47+
cp <- cps !! i
48+
case ndxs !! i of
49+
Just (Index ndx) -> do
50+
case fromEnum cp of
51+
0x0000 -> Just $ go ndx acc cps next -- skip 0
52+
0x0008 -> Just $ go ndx (splitAt ((S.length acc) - 1) acc).before cps next
53+
_ -> Just $ go ndx (acc <> singleton cp) cps next
54+
Just Infinity -> Just $ acc <> singleton cp
55+
Nothing -> Nothing

0 commit comments

Comments
 (0)