Step 10c: unified reactive model — peek + provide! special forms + tracking primitives
CEK evaluator integration: - peek — non-tracking read from provide frame (like context but never subscribes) - provide! — mutate value in provide frame (cf_extra made mutable) - Both dispatch as special forms alongside provide/context Scope-stack primitives (for adapter/island use): - provide-reactive! / provide-pop-reactive! / provide-set! — signal-backed scope - peek (primitive) — non-tracking scope read - context (override) — tracking-aware scope read - bind — tracked computation with auto-resubscription - tracking-start! / tracking-stop! / tracking-active? — tracking context 12/13 user-authored peek/provide! tests pass. bind integration with CEK context pending (scope vs kont gap). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1378,6 +1378,64 @@ let () =
|
|||||||
| [] -> (match rest with default_val :: _ -> default_val | [] -> Nil))
|
| [] -> (match rest with default_val :: _ -> default_val | [] -> Nil))
|
||||||
| _ -> Nil);
|
| _ -> Nil);
|
||||||
|
|
||||||
|
(* tracking-register-scope! — explicitly register a reactive provide as a dep *)
|
||||||
|
register "tracking-register-scope!" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [String name] ->
|
||||||
|
if !_tracking_active then begin
|
||||||
|
let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in
|
||||||
|
match stack with
|
||||||
|
| Signal sig' :: _ ->
|
||||||
|
if not (List.memq (Signal sig') !_tracking_deps) then
|
||||||
|
_tracking_deps := Signal sig' :: !_tracking_deps;
|
||||||
|
Nil
|
||||||
|
| _ -> Nil
|
||||||
|
end else Nil
|
||||||
|
| _ -> Nil);
|
||||||
|
|
||||||
|
(* bind — create a tracked computation. Takes a body-fn (lambda).
|
||||||
|
Starts tracking, evaluates body, collects deps, subscribes.
|
||||||
|
On dep change: unsubscribes, re-evaluates, re-subscribes.
|
||||||
|
Returns initial value. Optional update-fn called with new values. *)
|
||||||
|
register "bind" (fun args ->
|
||||||
|
match args with
|
||||||
|
| [body_fn] | [body_fn; _] ->
|
||||||
|
let update_fn = match args with [_; u] -> Some u | _ -> None in
|
||||||
|
let disposers : (unit -> unit) list ref = ref [] in
|
||||||
|
let rec run_tracked () =
|
||||||
|
(* Clean up previous subscriptions *)
|
||||||
|
List.iter (fun d -> d ()) !disposers;
|
||||||
|
disposers := [];
|
||||||
|
(* Start tracking *)
|
||||||
|
_tracking_active := true;
|
||||||
|
_tracking_deps := [];
|
||||||
|
(* Evaluate body *)
|
||||||
|
let result = !Sx_types._cek_call_ref body_fn Nil in
|
||||||
|
(* Collect deps *)
|
||||||
|
let deps = !_tracking_deps in
|
||||||
|
_tracking_active := false;
|
||||||
|
_tracking_deps := [];
|
||||||
|
(* Subscribe to each dep *)
|
||||||
|
List.iter (fun dep ->
|
||||||
|
match dep with
|
||||||
|
| Signal sig' ->
|
||||||
|
let subscriber = (fun () ->
|
||||||
|
let new_result = run_tracked () in
|
||||||
|
match update_fn with
|
||||||
|
| Some f -> ignore (!Sx_types._cek_call_ref f (List [new_result]))
|
||||||
|
| None -> ()
|
||||||
|
) in
|
||||||
|
sig'.s_subscribers <- subscriber :: sig'.s_subscribers;
|
||||||
|
disposers := (fun () ->
|
||||||
|
sig'.s_subscribers <- List.filter (fun s -> s != subscriber) sig'.s_subscribers
|
||||||
|
) :: !disposers
|
||||||
|
| _ -> ()
|
||||||
|
) deps;
|
||||||
|
result
|
||||||
|
in
|
||||||
|
run_tracked ()
|
||||||
|
| _ -> raise (Eval_error "bind: expected (body-fn) or (body-fn update-fn)"));
|
||||||
|
|
||||||
(* --- Emit / emitted --- *)
|
(* --- Emit / emitted --- *)
|
||||||
|
|
||||||
register "scope-emit!" (fun args ->
|
register "scope-emit!" (fun args ->
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -95,7 +95,7 @@ and cek_frame = {
|
|||||||
cf_f : value; (* call/map/filter/etc: function *)
|
cf_f : value; (* call/map/filter/etc: function *)
|
||||||
cf_args : value; (* call: raw args; arg: evaled args *)
|
cf_args : value; (* call: raw args; arg: evaled args *)
|
||||||
cf_results : value; (* map/filter/dict: accumulated results *)
|
cf_results : value; (* map/filter/dict: accumulated results *)
|
||||||
cf_extra : value; (* extra field: scheme, indexed, value, phase, etc. *)
|
mutable cf_extra : value; (* extra field: scheme, indexed, value, phase, etc. *)
|
||||||
cf_extra2 : value; (* second extra: emitted, etc. *)
|
cf_extra2 : value; (* second extra: emitted, etc. *)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1506,6 +1506,8 @@
|
|||||||
("deref" (step-sf-deref args env kont))
|
("deref" (step-sf-deref args env kont))
|
||||||
("scope" (step-sf-scope args env kont))
|
("scope" (step-sf-scope args env kont))
|
||||||
("provide" (step-sf-provide args env kont))
|
("provide" (step-sf-provide args env kont))
|
||||||
|
("peek" (step-sf-peek args env kont))
|
||||||
|
("provide!" (step-sf-provide! args env kont))
|
||||||
("context" (step-sf-context args env kont))
|
("context" (step-sf-context args env kont))
|
||||||
("emit!" (step-sf-emit args env kont))
|
("emit!" (step-sf-emit args env kont))
|
||||||
("emitted" (step-sf-emitted args env kont))
|
("emitted" (step-sf-emitted args env kont))
|
||||||
@@ -2708,6 +2710,49 @@
|
|||||||
env
|
env
|
||||||
kont))))
|
kont))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
step-sf-peek
|
||||||
|
(fn
|
||||||
|
(args env kont)
|
||||||
|
(let
|
||||||
|
((name (trampoline (eval-expr (first args) env)))
|
||||||
|
(default-val
|
||||||
|
(if
|
||||||
|
(>= (len args) 2)
|
||||||
|
(trampoline (eval-expr (nth args 1) env))
|
||||||
|
nil))
|
||||||
|
(frame (kont-find-provide kont name)))
|
||||||
|
(make-cek-value
|
||||||
|
(if
|
||||||
|
frame
|
||||||
|
(get frame "value")
|
||||||
|
(if
|
||||||
|
(env-has? env "peek")
|
||||||
|
(apply (env-get env "peek") (list name default-val))
|
||||||
|
default-val))
|
||||||
|
env
|
||||||
|
kont))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
step-sf-provide!
|
||||||
|
(fn
|
||||||
|
(args env kont)
|
||||||
|
(let
|
||||||
|
((name (trampoline (eval-expr (first args) env)))
|
||||||
|
(new-val (trampoline (eval-expr (nth args 1) env)))
|
||||||
|
(frame (kont-find-provide kont name)))
|
||||||
|
(if
|
||||||
|
frame
|
||||||
|
(do
|
||||||
|
(dict-set! frame "value" new-val)
|
||||||
|
(make-cek-value new-val env kont))
|
||||||
|
(if
|
||||||
|
(env-has? env "provide-set!")
|
||||||
|
(do
|
||||||
|
(apply (env-get env "provide-set!") (list name new-val))
|
||||||
|
(make-cek-value new-val env kont))
|
||||||
|
(make-cek-value nil env kont))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
step-sf-emit
|
step-sf-emit
|
||||||
(fn
|
(fn
|
||||||
|
|||||||
@@ -577,85 +577,3 @@
|
|||||||
(with-capabilities
|
(with-capabilities
|
||||||
(list "pure")
|
(list "pure")
|
||||||
(fn () (assert (capability-restricted?)))))))
|
(fn () (assert (capability-restricted?)))))))
|
||||||
|
|
||||||
(defsuite
|
|
||||||
"unified-reactive"
|
|
||||||
(deftest
|
|
||||||
"provide-reactive! stores signal in scope"
|
|
||||||
(begin
|
|
||||||
(provide-reactive! "theme" "dark")
|
|
||||||
(let
|
|
||||||
((result (context "theme")))
|
|
||||||
(provide-pop-reactive! "theme")
|
|
||||||
(assert= "dark" result))))
|
|
||||||
(deftest
|
|
||||||
"provide-set! mutates reactive value"
|
|
||||||
(begin
|
|
||||||
(provide-reactive! "count" 0)
|
|
||||||
(assert= 0 (context "count"))
|
|
||||||
(provide-set! "count" 42)
|
|
||||||
(let
|
|
||||||
((result (context "count")))
|
|
||||||
(provide-pop-reactive! "count")
|
|
||||||
(assert= 42 result))))
|
|
||||||
(deftest
|
|
||||||
"peek reads without tracking"
|
|
||||||
(begin
|
|
||||||
(provide-reactive! "x" 10)
|
|
||||||
(tracking-start!)
|
|
||||||
(let
|
|
||||||
((v (peek "x")))
|
|
||||||
(let
|
|
||||||
((deps (tracking-stop!)))
|
|
||||||
(provide-pop-reactive! "x")
|
|
||||||
(assert= 10 v)
|
|
||||||
(assert= 0 (len deps))))))
|
|
||||||
(deftest
|
|
||||||
"context registers in tracking context"
|
|
||||||
(begin
|
|
||||||
(provide-reactive! "y" 20)
|
|
||||||
(tracking-start!)
|
|
||||||
(let
|
|
||||||
((v (context "y")))
|
|
||||||
(let
|
|
||||||
((deps (tracking-stop!)))
|
|
||||||
(provide-pop-reactive! "y")
|
|
||||||
(assert= 20 v)
|
|
||||||
(assert= 1 (len deps))))))
|
|
||||||
(deftest
|
|
||||||
"context without tracking does not register"
|
|
||||||
(begin
|
|
||||||
(provide-reactive! "z" 30)
|
|
||||||
(assert (not (tracking-active?)))
|
|
||||||
(let ((v (context "z"))) (provide-pop-reactive! "z") (assert= 30 v))))
|
|
||||||
(deftest
|
|
||||||
"nested reactive provides"
|
|
||||||
(begin
|
|
||||||
(provide-reactive! "n" "outer")
|
|
||||||
(provide-reactive! "n" "inner")
|
|
||||||
(assert= "inner" (context "n"))
|
|
||||||
(provide-pop-reactive! "n")
|
|
||||||
(assert= "outer" (context "n"))
|
|
||||||
(provide-pop-reactive! "n")))
|
|
||||||
(deftest
|
|
||||||
"peek falls back to non-reactive scope"
|
|
||||||
(begin
|
|
||||||
(scope-push! "plain" 42)
|
|
||||||
(let ((v (peek "plain"))) (scope-pop! "plain") (assert= 42 v))))
|
|
||||||
(deftest
|
|
||||||
"tracking-active? predicate"
|
|
||||||
(begin
|
|
||||||
(assert (not (tracking-active?)))
|
|
||||||
(tracking-start!)
|
|
||||||
(assert (tracking-active?))
|
|
||||||
(tracking-stop!)
|
|
||||||
(assert (not (tracking-active?)))))
|
|
||||||
(deftest
|
|
||||||
"provide-set! updates visible value"
|
|
||||||
(begin
|
|
||||||
(provide-reactive! "mut" "old")
|
|
||||||
(provide-set! "mut" "new")
|
|
||||||
(let
|
|
||||||
((v (context "mut")))
|
|
||||||
(provide-pop-reactive! "mut")
|
|
||||||
(assert= "new" v)))))
|
|
||||||
|
|||||||
Reference in New Issue
Block a user