@@ -4,63 +4,101 @@ open System
44open FSharp.Data .Adaptive
55
66
7- /// An adaptive reader that allows to get operations since the last evaluation
7+ /// An adaptive reader that provides incremental access to changes (deltas).
8+ /// This is the foundation of the incremental update system in FSharp.Data.Adaptive.
9+ ///
10+ /// Readers maintain a position in the history and return only the changes since their
11+ /// last GetChanges call, enabling efficient O(k) updates where k = number of changes,
12+ /// rather than O(n) full recomputations where n = total collection size.
813type IOpReader < 'Delta > =
914 inherit IAdaptiveObject
1015
11- /// Dependency-aware evaluation of the reader
16+ /// Adaptively gets the changes (delta) since the last evaluation.
17+ /// Returns an empty delta if the reader is up-to-date.
18+ /// The token tracks dependencies for automatic change propagation.
1219 abstract member GetChanges: AdaptiveToken -> 'Delta
1320
14- /// An adaptive reader thath allows to get operations and also exposes its current state.
21+ /// An adaptive reader that provides both incremental changes and the current state.
22+ /// This is used by alist, aset, and amap to provide efficient incremental updates.
23+ ///
24+ /// The reader maintains:
25+ /// - Current state after applying all deltas
26+ /// - Position in the version history
27+ /// - Only deltas since the last read
1528[<Interface>]
1629type IOpReader < 'State , 'Delta > =
1730 inherit IOpReader< 'Delta>
1831
19- /// The Traceable instance for the reader.
32+ /// The Traceable instance defining how states and deltas interact.
33+ /// Provides operations like applying deltas, combining deltas, computing diffs, etc.
2034 abstract member Trace : Traceable < 'State , 'Delta >
2135
22- /// The latest state of the Reader.
23- /// Note that the state gets updated after each evaluation (GetChanges)
36+ /// The current state of the reader after applying all deltas.
37+ /// This state is updated incrementally each time GetChanges is called.
38+ /// Time complexity: O(1) access, state maintained incrementally.
2439 abstract member State: 'State
2540
26- /// Abstract base class for implementing IOpReader<_>
41+ /// Abstract base class for implementing custom incremental readers.
42+ /// Provides the core logic for tracking changes and returning deltas.
43+ ///
44+ /// Subclasses only need to implement Compute to define how to calculate changes.
45+ /// The base class handles:
46+ /// - Caching (returns empty delta when up-to-date)
47+ /// - Dependency tracking through AdaptiveToken
48+ /// - Optional delta transformation via Apply
2749[<AbstractClass>]
2850type AbstractReader < 'Delta >( empty : 'Delta ) =
2951 inherit AdaptiveObject()
30-
31- /// Adaptively compute deltas.
52+
53+ /// Computes the delta since the last evaluation.
54+ /// Called only when the reader is out-of-date.
55+ /// Subclasses implement this to define incremental update logic.
3256 abstract member Compute: AdaptiveToken -> 'Delta
3357
34- /// Applies the delta to the current state and returns the 'effective' delta.
58+ /// Optionally transforms the computed delta before returning it.
59+ /// Default implementation returns the delta unchanged.
60+ /// Override to implement delta filtering, normalization, or other transformations.
3561 abstract member Apply: 'Delta -> 'Delta
3662 default x.Apply o = o
37-
38- /// Adaptively get the latest deltas (or empty if up-to-date).
63+
64+ /// Adaptively gets the latest deltas (or empty if up-to-date).
65+ /// Returns empty delta when nothing changed, computed delta when out-of-date.
66+ /// Time complexity: O(1) when cached, O(compute cost) when invalidated.
3967 member x.GetChanges ( token : AdaptiveToken ) =
4068 x.EvaluateAlways token ( fun token ->
4169 if x.OutOfDate then
4270 x.Compute token |> x.Apply
4371 else
4472 empty
45- )
73+ )
4674
4775 interface IOpReader< 'Delta> with
4876 member x.GetChanges c = x.GetChanges c
4977
50- /// Abstract base class for implementing IOpReader<_,_>
78+ /// Abstract base class for implementing stateful incremental readers.
79+ /// Maintains current state and automatically applies deltas to update it.
80+ ///
81+ /// This is the typical base class for alist/aset/amap operations like map, filter, etc.
82+ /// The base class handles:
83+ /// - State management (maintained incrementally)
84+ /// - Delta application via Traceable
85+ /// - Returning effective deltas after state updates
5186[<AbstractClass>]
5287type AbstractReader < 'State , 'Delta >( trace : Traceable < 'State , 'Delta >) =
5388 inherit AbstractReader< 'Delta>( trace.tmonoid.mempty)
5489
5590 let mutable state = trace.tempty
5691
5792 /// Applies the delta to the current state and returns the 'effective' delta.
93+ /// The effective delta reflects what actually changed (e.g., filtered results).
94+ /// Time complexity: O(k) where k = size of delta
5895 override x.Apply o =
5996 let ( s , o ) = trace.tapplyDelta state o
6097 state <- s
6198 o
6299
63- /// The reader's current content.
100+ /// The reader's current state after applying all deltas.
101+ /// Maintained incrementally, always reflects the latest accumulated state.
64102 member x.State = state
65103
66104 interface IOpReader< 'State, 'Delta> with
@@ -109,7 +147,16 @@ type AbstractDirtyReader<'T, 'Delta when 'T :> IAdaptiveObject>(t: Monoid<'Delta
109147 interface IOpReader< 'Delta> with
110148 member x.GetChanges c = x.GetChanges c
111149
112- /// Linked list node used by the system to represent a 'version' in the History
150+ /// Linked list node representing a version in the History chain.
151+ /// Each node contains:
152+ /// - BaseState: The state at this version
153+ /// - Value: The delta (changes) from the previous version
154+ /// - Prev/Next: Weak references forming the version chain
155+ /// - RefCount: Number of readers currently at this version
156+ ///
157+ /// This structure enables O(k) access where k = changes since last read.
158+ /// Nodes with RefCount = 0 can be garbage collected.
159+ /// Weak references allow automatic cleanup of unreferenced versions.
113160[<AllowNullLiteral>]
114161type internal RelevantNode < 'State , 'T > =
115162 class
@@ -118,13 +165,34 @@ type internal RelevantNode<'State, 'T> =
118165 val mutable public RefCount : int
119166 val mutable public BaseState : 'State
120167 val mutable public Value : 'T
121-
168+
122169 new ( p, s, v, n) = { Prev = p; Next = n; RefCount = 0 ; BaseState = s; Value = v }
123170 end
124171
125- /// History and HistoryReader are the central implementation for traceable data-types.
126- /// The allow to construct a dependent History (by passing an input-reader) or imperatively
127- /// performing operations on the history while keeping track of all output-versions that may exist.
172+ /// History is THE central mechanism that makes incremental adaptive collections work.
173+ ///
174+ /// It maintains a doubly-linked version chain where each node contains:
175+ /// - A delta (the changes that occurred)
176+ /// - The state after applying the delta
177+ /// - Weak references to previous/next versions
178+ /// - Reference count of readers at this version
179+ ///
180+ /// Key features:
181+ /// - Multiple readers can be at different versions simultaneously
182+ /// - Each reader gets O(k) access to deltas where k = changes since last read
183+ /// - Automatic pruning removes versions no readers need
184+ /// - Weak references allow GC of unreferenced parts of the history
185+ /// - Can be driven by an input reader (dependent) or imperatively (via Perform)
186+ /// - Efficiently handles many readers on the same history
187+ ///
188+ /// This is how alist/aset/amap provide efficient incremental updates.
189+ /// When you call GetChanges on a reader, it walks from its last version
190+ /// to the current version, accumulating only the deltas it needs.
191+ ///
192+ /// Memory management:
193+ /// - Nodes are pruned when no readers reference them (every 100 appends)
194+ /// - Weak references allow GC of dead readers
195+ /// - Single-reader case is optimized (deltas accumulated in current node)
128196type History < 'State , 'Delta > private ( input : voption < Lazy < IOpReader < 'Delta >>>, t : Traceable < 'State , 'Delta >, finalize : 'Delta -> unit ) =
129197 inherit AdaptiveObject()
130198
@@ -343,14 +411,28 @@ type History<'State, 'Delta> private(input: voption<Lazy<IOpReader<'Delta>>>, t:
343411 | ValueNone ->
344412 ()
345413
346- /// The current state of the history
414+ /// The current state of the history after applying all operations.
415+ /// This is the "latest" version that new readers will start from.
416+ /// Time complexity: O(1)
347417 member x.State = state
348418
349- /// The traceable instance used by the history
419+ /// The Traceable instance defining how states and deltas interact.
420+ /// Provides operations like applying deltas, combining deltas, pruning, etc.
350421 member x.Trace = t
351422
352- /// Imperatively performs operations on the history (similar to ModRef.Value <- ...).
353- /// Since the history may need to be marked a Transaction needs to be current.
423+ /// Imperatively performs an operation on the history.
424+ /// This is how changeable collections (clist, cset, cmap) update their history.
425+ ///
426+ /// The operation is:
427+ /// 1. Applied to the current state
428+ /// 2. Appended to the version chain (if non-empty)
429+ /// 3. Made available to all readers via GetChanges
430+ ///
431+ /// Must be called within a transaction (like cval.Value <- ...).
432+ /// Returns true if the operation effectively changed the state.
433+ ///
434+ /// Example:
435+ /// transact (fun () -> history.Perform(IndexListDelta.add index value))
354436 member x.Perform ( op : 'Delta ) =
355437 let changed = lock x ( fun () -> append op)
356438 if changed then
@@ -359,9 +441,13 @@ type History<'State, 'Delta> private(input: voption<Lazy<IOpReader<'Delta>>>, t:
359441 else
360442 false
361443
362- /// Imperatively performs operations on the history (similar to ModRef.Value <- ...)
363- /// and assumes that newState represents the current history-state with the given operations applied. (hence the Unsafe suffix)
364- /// Since the history may need to be marked a Transaction needs to be current.
444+ /// Imperatively performs an operation and directly sets the new state.
445+ /// This is an optimization when you've already computed the new state.
446+ ///
447+ /// UNSAFE because it assumes newState is correct (state after applying op).
448+ /// Only use when you've computed the new state yourself for performance.
449+ /// Must be called within a transaction.
450+ /// Returns true if the operation effectively changed the state.
365451 member x.PerformUnsafe ( newState : 'State , op : 'Delta ) =
366452 let changed = lock x ( fun () -> appendUnsafe newState op)
367453 if changed then
@@ -398,26 +484,43 @@ type History<'State, 'Delta> private(input: voption<Lazy<IOpReader<'Delta>>>, t:
398484 node, res
399485 )
400486
401- /// Adaptively gets the history'State current state
487+ /// Adaptively gets the history's current state.
488+ /// If the history has an input reader, pulls changes from it first.
489+ /// Returns the current state after all operations have been applied.
490+ /// Time complexity: O(1) if up-to-date, O(k) if pulling k changes from input
402491 member x.GetValue ( token : AdaptiveToken ) =
403492 x.EvaluateAlways token ( fun token ->
404493 x.Update token
405494 state
406495 )
407496
408- /// Creates a new reader on the history
497+ /// Creates a new reader starting at the current version.
498+ /// The reader will track its position and return only incremental changes on GetChanges.
499+ /// Multiple readers can coexist, each maintaining their own position.
500+ ///
501+ /// This is how alist.GetReader(), aset.GetReader(), etc. work internally.
502+ /// Time complexity: O(1)
409503 member x.NewReader () =
410- let reader = new HistoryReader< 'State, 'Delta>( x)
504+ let reader = new HistoryReader< 'State, 'Delta>( x)
411505 reader :> IOpReader< 'State, 'Delta>
412-
413- /// Creates a new reader on the history
506+
507+ /// Creates a new reader with a mapping to a different view.
508+ /// Useful for implementing derived operations that need different state/delta types.
509+ ///
510+ /// The mapping function transforms each delta before the reader returns it.
511+ /// The trace defines the view's state and delta algebra.
512+ ///
513+ /// Example: mapping IndexListDelta to HashSetDelta for AList.toASet
414514 member x.NewReader ( trace : Traceable < 'ViewState , 'ViewDelta >, mapping : 'State -> 'Delta -> 'ViewDelta ) =
415- let reader = new HistoryReader< 'State, 'Delta, 'ViewState, 'ViewDelta>( x, mapping, trace)
515+ let reader = new HistoryReader< 'State, 'Delta, 'ViewState, 'ViewDelta>( x, mapping, trace)
416516 reader :> IOpReader< 'ViewState, 'ViewDelta>
417-
418- /// Creates a new reader on the history
517+
518+ /// Creates a new reader with a stateless delta mapping.
519+ /// Simpler overload when the mapping doesn't need the current state.
520+ ///
521+ /// Example: filtering deltas, transforming element types, etc.
419522 member x.NewReader ( trace : Traceable < 'ViewState , 'ViewDelta >, mapping : 'Delta -> 'ViewDelta ) =
420- let reader = new HistoryReader< 'State, 'Delta, 'ViewState, 'ViewDelta>( x, ( fun _ v -> mapping v), trace)
523+ let reader = new HistoryReader< 'State, 'Delta, 'ViewState, 'ViewDelta>( x, ( fun _ v -> mapping v), trace)
421524 reader :> IOpReader< 'ViewState, 'ViewDelta>
422525
423526 interface IAdaptiveValue with
@@ -433,12 +536,29 @@ type History<'State, 'Delta> private(input: voption<Lazy<IOpReader<'Delta>>>, t:
433536 interface IAdaptiveValue< 'State> with
434537 member x.GetValue t = x.GetValue t
435538
539+ /// Creates an imperative history (no input reader).
540+ /// Used by changeable collections (clist, cset, cmap).
541+ /// Operations are added via Perform().
542+ /// The finalize callback is called on deltas when they're discarded.
436543 new ( t : Traceable < 'State , 'Delta >, finalize : 'Delta -> unit ) = History< 'State, 'Delta>( ValueNone, t, finalize)
544+
545+ /// Creates a dependent history driven by an input reader.
546+ /// Used by derived operations (map, filter, etc.) that transform another collection.
547+ /// The input reader provides deltas that are automatically appended.
548+ /// The finalize callback is called on deltas when they're discarded.
437549 new ( input : unit -> IOpReader < 'Delta >, t : Traceable < 'State , 'Delta >, finalize : 'Delta -> unit ) = History< 'State, 'Delta>( ValueSome ( lazy ( input())), t, finalize)
550+
551+ /// Creates an imperative history with no finalization.
552+ /// Most common constructor for simple changeable collections.
438553 new ( t : Traceable < 'State , 'Delta >) = History< 'State, 'Delta>( ValueNone, t, ignore)
554+
555+ /// Creates a dependent history with no finalization.
556+ /// Most common constructor for derived operations.
439557 new ( input : unit -> IOpReader < 'Delta >, t : Traceable < 'State , 'Delta >) = History< 'State, 'Delta>( ValueSome ( lazy ( input())), t, ignore)
440558
441- /// HistoryReader implements IOpReader<_,_> and takes care of managing versions correctly.
559+ /// HistoryReader maintains a position in a History and provides incremental access to changes.
560+ /// Each reader tracks its own version and walks forward through the version chain on GetChanges.
561+ /// This is the implementation behind alist/aset/amap readers.
442562and internal HistoryReader < 'State , 'Delta >( h : History < 'State , 'Delta >) =
443563 inherit AdaptiveObject()
444564 let trace = h.Trace
0 commit comments