flan/test/programs/fn-escape.flan

216 lines
6.8 KiB
Plaintext

;; Closures that outlive the frame they were made in. The environment is
;; allocated by the collector, so a capturing fn may be returned, kept in a
;; Vec, an Option field of a struct or a global, and called after any number
;; of collections. Every line below is called after a forced collection that
;; would have freed an environment nobody rooted.
;;
;; Capture is by value: the environment holds copies of the locals taken when
;; the fn was made. A counter shares state through a captured dyn map, which
;; is a reference to one object on the collector's heap.
(declare gc-collect [] () "flan_gc_collect")
(defn churn [] ()
(let [i 0]
(while (< i 2000)
(let [v (vec-new dyn)] (push v i) (push v "junk"))
(set i (+ i 1))))
(gc-collect))
(defn make-adder [n i64] (Fn [i64] i64)
(fn [x] (+ x n)))
;; Two captures deep: the inner fn captures the outer one's copy of [base],
;; and the returned value captures a function value that captures.
(defn make-scaled [base i64 k i64] (Fn [i64] i64)
(let [add (make-adder base)]
(fn [x] (* k (add x)))))
(defn keep [f (Fn [i64] i64)] (Fn [i64] i64) f)
;; A function value read back out of an environment and handed on: [sneak]'s
;; literal captures [g], and [getf] returns the copy.
(defn getf [f (Fn [] (Fn [i64] i64))] (Fn [i64] i64) (f))
(defn sneak [g (Fn [i64] i64)] (Fn [i64] i64) (getf (fn [] g)))
;; Stored through a pointer into a slot of the caller's frame.
(defn stash [p (Ptr (Fn [i64] i64)) f (Fn [i64] i64)] () (set (deref p) f))
(defn make-counter [] (Fn [] i64)
(let [st {:n 0}]
(fn []
(put st :n (+ (get st :n) 1))
(i64 (get st :n)))))
;; A dyn captured and returned: the text is on the collector's heap and
;; reachable only through the environment.
(defn make-greeter [who] (Fn [] i64)
(let [msg (vec-new dyn)]
(push msg who)
(push msg "and")
(push msg who)
(fn [] (i64 (length msg)))))
(defstruct Button [label string on-click (Option (Fn [] i64))])
(defonce handler (Option (Fn [i64] i64)))
(defn call-opt [o (Option (Fn [i64] i64)) x i64] i64
(match o
(Some f) (f x)
None -1))
(defn double [x i64] i64 (* 2 x))
;; A closure made as an argument and held while the next argument collects —
;; kept by the callee, so it is one the collector owns — and one collected
;; for while the callee runs, before the callee has put it anywhere. A
;; parameter of function type is not rooted by the callee; the caller holds
;; it.
(defn apply-to [f (Fn [i64] i64) x i64] i64 (f x))
(defn churn-1 [] i64 (churn) 1)
(defonce last-fn (Option (Fn [i64] i64)))
(defn remember [f (Fn [i64] i64) x i64] i64
(churn)
(set last-fn (Some f))
(f x))
;; A handler clause keeps its copies on the establishing frame, and may now
;; capture a dyn and a closure like an fn may.
(defstruct Ping [n i64])
(defonce heard i64)
(defn pinged [x i64] i64 (signal (Ping {.n x})) x)
(defn keep-handled [f (Fn [i64] i64)] (Fn [i64] i64)
(handler-bind [(Ping [c] (set heard 0))] f))
(defn handled [] ()
(let [names (vec-new dyn)
f (make-adder 50)]
(push names "a")
(push names "b")
(handler-bind [(Ping [c] (churn) (set heard (+ (f (.n c)) (i64 (length names)))))]
(pinged 1))
(println heard)))
;; A data type's case holding a function value, in a Vec, and a Vec of Vecs.
;; The collector names every case's function-value word and follows only the
;; ones that hold an environment it made.
(defdata Action
[Idle
(Run [f (Option (Fn [i64] i64)) n i64])
(Pair [k i64 g (Option (Fn [i64] i64))])])
(defn act [a Action x i64] i64
(match a
Idle 0
(Run f n) (+ n (call-opt f x))
(Pair k g) (+ k (call-opt g x))))
(defn shapes [] ()
(let [acts (vec-new Action)
a (arena-new 65536)
outer (vec-new (Vec (Fn [i64] i64)) a)]
(push acts (Action.Run {.f (Some (make-adder 1)) .n 10}))
(push acts (Action.Pair {.k 20 .g (Some (make-adder 2))}))
(push acts Action.Idle)
(let [inner (vec-new (Fn [i64] i64) a)]
(push inner (make-adder 3))
(push outer inner))
(churn)
(println (+ (act (at acts 0) 1) (+ (act (at acts 1) 1) (act (at acts 2) 1)))
((at (at outer 0) 0) 1))))
;; A data type holding a Vec of itself: the collector walks as deep as the
;; data goes, and the function value two levels down is still found.
(defdata Tree [(Node [f (Option (Fn [i64] i64)) kids (Vec Tree)])])
(defn tree-sum [t Tree x i64] i64
(match t
(Node f kids)
(let [s (call-opt f x)
i 0]
(while (< i (length kids))
(set s (+ s (tree-sum (at kids i) x)))
(set i (+ i 1)))
s)))
(defn trees [] ()
(let [a (arena-new 65536)
leaf-kids (vec-new Tree a)
kids (vec-new Tree a)]
(push kids (Tree.Node {.f (Some (make-adder 5)) .kids leaf-kids}))
(let [root (Tree.Node {.f None .kids kids})]
(churn)
(println (tree-sum root 1)))))
;; Environments nobody holds are collected: a hundred thousand of them made
;; and dropped leave the heap as small as it was.
(declare gc-live-bytes [] i64 "flan_gc_live_bytes")
(defn many [] ()
(let [sum (i64 0)]
(dotimes [i 100000]
(set sum (+ sum ((make-adder 1) 0))))
(gc-collect)
(println sum (< (gc-live-bytes) 1000000))))
(defn main [] i32
;; Returned, then called after a collection.
(let [add5 (make-adder 5)]
(churn)
(println (add5 10)))
;; Nested, and passed through a function that hands its parameter back.
(let [f (keep (make-scaled 1 3))]
(churn)
(println (f 4)))
;; Out of an environment, through a handler-bind's value, and through a
;; pointer.
(let [f (keep-handled (sneak (make-adder 20)))
slot (make-adder 0)]
(stash (addr slot) (make-adder 7))
(churn)
(println (f 1) (slot 1)))
;; Held beside a sibling operand that collects.
(let [n 40]
(println (apply-to (fn [x] (+ x n)) (churn-1))
(remember (fn [x] (+ x n)) 1)))
;; A Vec of function values, one capture per iteration plus a widened name.
(let [fs (vec-new (Fn [i64] i64))]
(let [i 0]
(while (< i 5)
(push fs (make-adder (* i 100)))
(set i (+ i 1))))
(push fs double)
(churn)
(let [total (i64 0)]
(let [i 0]
(while (< i (length fs))
(set total (+ total ((at fs i) 1)))
(set i (+ i 1))))
(println total)))
;; A counter over a captured dyn map: two calls on one value see one state.
(let [c (make-counter)]
(c)
(churn)
(c)
(println (c)))
;; A captured dyn.
(let [g (make-greeter "world")]
(churn)
(println (g)))
;; A struct field and a global, both through Option.
(let [b (Button {.label "ok" .on-click (Some (make-counter))})]
(churn)
(match (.on-click b)
(Some f) (do (f) (println (f)))
None (println "none")))
(set handler (Some (make-adder 1000)))
(churn)
(println (call-opt handler 7))
(handled)
(shapes)
(trees)
(many)
0)