🥮 mooncakes.io

    #Duplix

    Note: This module is experimental.

    Duplix is a reactive graph built from two paired strands: value dependencies and dirty propagation.

    It aims to provide bounded retention and does not require manual disposal to avoid memory leak, especially in RC runtime. It also provides lazy computation, cutoff propagation, and at-most-once recomputation per node after each update.

    #Core idea

    parent <--derived--- child1 <--derived--- child2

    For a reactive graph child2 -> child1 -> parent, Duplix splits the graph into two strands:

    • Dirty propagation: propagates dirty flags after values change.
    • Value dependencies: update node values when nodes are read.

    Initially, the whole graph is dirty, so the dirty-propagation edges are not built yet:

    Node(parent) --ref-> Node(child1) --ref-> Node(child2) | | | ref ref ref | | | v v v Dirty(parent) Dirty(child1) Dirty(child2)

    When the user reads parent, Duplix recomputes the value through the value dependencies and builds the dirty-propagation edges for the next update:

    Node(parent) --ref-> Node(child1) --ref-> Node(child2) | | | ref ref ref | | | v v v Dirty(parent) <-ref-- Dirty(child1) <-ref-- Dirty(child2)

    The next time child2 changes, Duplix marks the related dirty nodes and cuts the dirty-propagation edges. This brings the graph back to the initial dirty state, where the edges can be rebuilt on demand by the next read.

    At no point does the value graph contain a reference cycle.

    DirtyFlag

    type DirtyFlag

    DirtyFlag(parent) <--ref--- DirtyFlag(child)
    impl Eq for DirtyFlag

    Node

    type Node[T]

    child ---derived---> parent Node(parent) ---ref---> Node(child) | | ref ref | | v v DirtyFlag(parent) <---ref--- DirtyFlag(child)
    impl Compare for Node[T]
    impl Eq for Node[T]

    Node::assoc

    Node::assoc_by

    fn[K : Hash + Eq, V : Eq, C : Eq] Node::assoc_by(a : Node[
    Vector
    [V]], f : (K, Node[V]) -> Node[C], by~ : (V) -> K) -> Node[
    Vector
    [C]]

    Node::bind

    fn[A : Eq, B : Eq] Node::bind(a : Node[A], f : (A) -> Node[B]) -> Node[B]

    a -----+ |----> output
    b1 or b2 ----+

    Node::enumerate

    fn[E :
    Enumerate
    + Eq, A : Eq] Node::enumerate(map : Node[E], f : (E) -> Node[A]) -> Node[A]

    Node::enumerate_by

    fn[E : Eq, A : Eq] Node::enumerate_by(map : Node[E], f : (E) -> Node[A], by~ : (E) -> String) -> Node[A]

    Node::map

    fn[A : Eq, B] Node::map(a : Node[A], f : (A) -> B) -> Node[B]

    child_a ---derived---> parent_b

    Node::map1

    fn[A : Eq, B] Node::map1(a : Node[A], f : (A) -> B) -> Node[B]

    Node::map10

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq, I : Eq, J : Eq, K] Node::map10(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : Node[G], h : Node[H], i : Node[I], j : Node[J], k : (A, B, C, D, E, F, G, H, I, J) -> K) -> Node[K]

    Node::map11

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq, I : Eq, J : Eq, K : Eq, L] Node::map11(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : Node[G], h : Node[H], i : Node[I], j : Node[J], k : Node[K], l : (A, B, C, D, E, F, G, H, I, J, K) -> L) -> Node[L]

    Node::map12

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq, I : Eq, J : Eq, K : Eq, L : Eq, M] Node::map12(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : Node[G], h : Node[H], i : Node[I], j : Node[J], k : Node[K], l : Node[L], m : (A, B, C, D, E, F, G, H, I, J, K, L) -> M) -> Node[M]

    Node::map2

    fn[A : Eq, B : Eq, C] Node::map2(a : Node[A], b : Node[B], f : (A, B) -> C) -> Node[C]

    Node::map3

    fn[A : Eq, B : Eq, C : Eq, D] Node::map3(a : Node[A], b : Node[B], c : Node[C], f : (A, B, C) -> D) -> Node[D]

    Node::map4

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E] Node::map4(a : Node[A], b : Node[B], c : Node[C], d : Node[D], f : (A, B, C, D) -> E) -> Node[E]

    Node::map5

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F] Node::map5(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : (A, B, C, D, E) -> F) -> Node[F]

    Node::map6

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G] Node::map6(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : (A, B, C, D, E, F) -> G) -> Node[G]

    Node::map7

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H] Node::map7(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : Node[G], h : (A, B, C, D, E, F, G) -> H) -> Node[H]

    Node::map8

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq, I] Node::map8(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : Node[G], h : Node[H], i : (A, B, C, D, E, F, G, H) -> I) -> Node[I]

    Node::map9

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq, I : Eq, J] Node::map9(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : Node[G], h : Node[H], i : Node[I], j : (A, B, C, D, E, F, G, H, I) -> J) -> Node[J]

    Node::read

    fn[A : Eq] Node::read(a : Node[A]) -> A

    Node::switch

    fn[E :
    Enumerate
    + Eq, A : Eq] Node::switch(a : Node[E], f : (E) -> Node[A]) -> Node[A]

    Node::switch_by

    fn[E : Eq, A : Eq] Node::switch_by(a : Node[E], f : (E) -> Node[A], by~ : (E) -> String) -> Node[A]

    Node::zip10

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq, I : Eq, J : Eq] Node::zip10(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : Node[G], h : Node[H], i : Node[I], j : Node[J]) -> Node[(A, B, C, D, E, F, G, H, I, J)]

    Node::zip11

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq, I : Eq, J : Eq, K : Eq] Node::zip11(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : Node[G], h : Node[H], i : Node[I], j : Node[J], k : Node[K]) -> Node[(A, B, C, D, E, F, G, H, I, J, K)]

    Node::zip12

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq, I : Eq, J : Eq, K : Eq, L : Eq] Node::zip12(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : Node[G], h : Node[H], i : Node[I], j : Node[J], k : Node[K], l : Node[L]) -> Node[(A, B, C, D, E, F, G, H, I, J, K, L)]

    Node::zip2

    fn[A : Eq, B : Eq] Node::zip2(a : Node[A], b : Node[B]) -> Node[(A, B)]

    Node::zip3

    fn[A : Eq, B : Eq, C : Eq] Node::zip3(a : Node[A], b : Node[B], c : Node[C]) -> Node[(A, B, C)]

    Node::zip4

    fn[A : Eq, B : Eq, C : Eq, D : Eq] Node::zip4(a : Node[A], b : Node[B], c : Node[C], d : Node[D]) -> Node[(A, B, C, D)]

    Node::zip5

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq] Node::zip5(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E]) -> Node[(A, B, C, D, E)]

    Node::zip6

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq] Node::zip6(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F]) -> Node[(A, B, C, D, E, F)]

    Node::zip7

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq] Node::zip7(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : Node[G]) -> Node[(A, B, C, D, E, F, G)]

    Node::zip8

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq] Node::zip8(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : Node[G], h : Node[H]) -> Node[(A, B, C, D, E, F, G, H)]

    Node::zip9

    fn[A : Eq, B : Eq, C : Eq, D : Eq, E : Eq, F : Eq, G : Eq, H : Eq, I : Eq] Node::zip9(a : Node[A], b : Node[B], c : Node[C], d : Node[D], e : Node[E], f : Node[F], g : Node[G], h : Node[H], i : Node[I]) -> Node[(A, B, C, D, E, F, G, H, I)]

    ScopeId

    type ScopeId derive(Eq, Hash)

    ScopeId::cleanup

    fn ScopeId::cleanup(id : ScopeId) -> Unit

    constant

    fn[T] constant(x : T) -> Node[T]

    input

    fn[T : Eq] input(x : T) -> (Node[T], (T) -> Unit)

    on_cleanup

    fn on_cleanup(f : () -> Unit) -> Unit

    with_scope

    fn[A] with_scope(f : (ScopeId) -> A) -> A