; feed/fanout — THE SHOWCASE. Fan activities out to followers via the APL outer ; product (∘.×). activities ∘.× audience → an (activity × follower) matrix of ; inbox events; flatten to a vector; guard-keep only real follow edges. ; ; Requires: lib/apl/runtime.sx, lib/feed/normalize.sx, lib/feed/stream.sx. ; ; NOTE: apl-outer's combiner result is run through (if (scalar? r) (disclose r) r). ; A bare dict counts as a scalar (shape ()) and disclose nils it — so the combiner ; must (enclose ...) its event dict; apl-outer then discloses it back intact. ; --- graph: {followee -> (list of followers)} ------------------------------- (define feed/followers (fn (graph user) (get graph user (list)))) ; build a graph from (follower followee) edges: "follower follows followee" (define feed/follow-graph (fn (edges) (reduce (fn (g e) (let ((follower (first e)) (followee (nth e 1))) (assoc g followee (append (feed/followers g followee) (list follower))))) {} edges))) ; --- helpers ---------------------------------------------------------------- ; unwrap an apl-scalar (has :ravel) back to its value; pass activities through (define feed/-val (fn (x) (if (and (= (type-of x) "dict") (has-key? x :ravel)) (disclose x) x))) (define feed/-elem? (fn (x lst) (some (fn (y) (equal? x y)) lst))) (define feed/-distinct (fn (lst) (if (= (len lst) 0) (list) (get (apl-unique (make-array (list (len lst)) lst)) :ravel)))) ; rank-2 matrix -> rank-1 stream of its ravel (define feed/-flatten (fn (arr) (feed/stream (get arr :ravel)))) ; distinct receivers across the whole graph, sorted for determinism ; (dict key order is unspecified, so sort to pin audience/recipient ordering) (define feed/audience (fn (graph) (sort (feed/-distinct (reduce (fn (acc k) (append acc (feed/followers graph k))) (list) (keys graph)))))) ; --- the outer product ------------------------------------------------------ ; one (activity, follower) inbox event, enclosed so apl-outer keeps the dict (define feed/-mk-event (fn (a f) (enclose {:activity (feed/-val a) :to (feed/-val f)}))) ; keep events where :to actually follows the activity's actor (define feed/-edge? (fn (graph) (fn (ev) (feed/-elem? (get ev :to) (feed/followers graph (get (get ev :activity) :actor)))))) ; fanout — activities ∘.× audience, flatten, guard-keep real edges (define feed/fanout (fn (stream graph) (let ((matrix (apl-outer feed/-mk-event stream (feed/stream (feed/audience graph))))) (feed/filter (feed/-flatten matrix) (feed/-edge? graph))))) ; --- inbox queries ---------------------------------------------------------- (define feed/inbox-for (fn (inbox user) (feed/filter inbox (fn (ev) (equal? (get ev :to) user))))) (define feed/recipients (fn (inbox) (feed/-distinct (map (fn (ev) (get ev :to)) (feed/items inbox))))) ; the activities (unwrapped) destined for a user (define feed/inbox-activities (fn (inbox user) (map (fn (ev) (get ev :activity)) (feed/items (feed/inbox-for inbox user)))))