117 lines
4.2 KiB
Plaintext
117 lines
4.2 KiB
Plaintext
;; Capture by value into a stack environment — spec-memory.md's case 2.
|
|
;;
|
|
;; An fn is still lifted into a function of its own, but it is no longer handed
|
|
;; nothing but its parameters: a local of the enclosing function that it names
|
|
;; is *copied* into an environment on that function's frame when the value is
|
|
;; made, and the lifted body reads the copy. The value is the code address and
|
|
;; that environment beside it, which is why a callee that knows only
|
|
;; (Fn [i32] i32) can still call it.
|
|
;;
|
|
;; What is not here is the escaping half — a value carrying an environment may
|
|
;; not outlive the frame the copies are on, and fn-escape*.flan is where each
|
|
;; of those refusals is written down.
|
|
|
|
(defn double [x i32] i32 (* x 2))
|
|
|
|
(defn apply2 [f (Fn [i32] i32) x i32] i32 (f x))
|
|
(defn call0 [f (Fn [] i32)] i32 (f))
|
|
(defn twice [f (Fn [i32] i32) x i32] i32 (f (f x)))
|
|
|
|
;; The copy is taken where the value is made and not where it is read, and
|
|
;; this is what proves it: the local is changed *after* the fn value exists
|
|
;; and before it is called, through a pointer, so nothing about the order can
|
|
;; be an accident of evaluation.
|
|
(defn bump-then-call [f (Fn [] i32) p (Ptr i32)] i32
|
|
(set (deref p) 99)
|
|
(f))
|
|
|
|
(defstruct Pt [x i32 y i32])
|
|
|
|
(defstruct TooBig [n i32])
|
|
(defonce seen i32)
|
|
|
|
(defn checked [x i32] i32
|
|
(when (> x 100) (signal (TooBig {.n x})))
|
|
x)
|
|
|
|
;; A handler clause is lifted the same way and captures the same way, and is
|
|
;; sound with nothing left over: a handler frame is popped by the body that
|
|
;; pushed it, so the establishing frame is alive whenever the clause runs.
|
|
;; [budget] is read out of the environment; the accumulator is a global,
|
|
;; because a captured copy is a copy and a store into one would leave the
|
|
;; local it came from as it was.
|
|
(defn handles [] i32
|
|
(let [budget 1000
|
|
xs [5 200 7 300]
|
|
s (slice xs 0 4)
|
|
t 0]
|
|
(handler-bind [(TooBig [c] (set seen (+ seen (+ budget (.n c)))))]
|
|
(dotimes [i 4]
|
|
(set t (+ t (checked (at s i))))))
|
|
(print t) (print " ") (println seen)
|
|
seen))
|
|
|
|
(defn main [] i32
|
|
;; The motivating program.
|
|
(let [bonus 10]
|
|
(println (apply2 (fn [x] (+ x bonus)) 5)))
|
|
|
|
;; Copy at creation: the fn answers 1 and the local is 99.
|
|
(let [n 1]
|
|
(print (bump-then-call (fn [] n) (addr n)))
|
|
(print " ")
|
|
(println n))
|
|
|
|
;; What may be captured. A string and a slice are two words copied as two
|
|
;; words — the bytes stay whoever's they were, which is fine exactly while
|
|
;; the value cannot outlive the frame that owns them. A struct and a fixed
|
|
;; array are copied whole. A function value is copied as a function value.
|
|
(let [s "hi"
|
|
arr [1 2 3 4]
|
|
sl (slice arr 0 4)
|
|
p (Pt {.x 3 .y 4})
|
|
g double]
|
|
(println (call0 (fn [] (i32 (length s)))))
|
|
(println (call0 (fn [] (at sl 2))))
|
|
(println (call0 (fn [] (+ (.x p) (.y p)))))
|
|
(println (call0 (fn [] (at arr 3))))
|
|
(println (apply2 (fn [x] (g (+ x 1))) 4)))
|
|
|
|
;; An fn inside an fn, each capturing. The inner one names a local neither
|
|
;; of them declared, so the outer one captures it too and the inner one
|
|
;; copies the outer one's copy.
|
|
(let [a 100
|
|
b 20]
|
|
(println (apply2 (fn [x] (+ x (call0 (fn [] (+ a b))))) 3)))
|
|
|
|
;; A loop variable: what the fn sees is the value at the iteration it was
|
|
;; made on, not the last one. 0 + 1 + 2 + 3.
|
|
(let [total 0]
|
|
(dotimes [i 4]
|
|
(set total (+ total (call0 (fn [] i)))))
|
|
(println total))
|
|
|
|
;; And the same again where the loop variable is stepped by a set in a
|
|
;; while rather than by a dotimes, which is a store into the slot the copy
|
|
;; is taken from: 100 + 101 + 102.
|
|
(println
|
|
(let [base 100 i 0 acc 0]
|
|
(while (< i 3)
|
|
(set acc (+ acc (call0 (fn [] (+ base i)))))
|
|
(set i (+ i 1)))
|
|
acc))
|
|
|
|
;; Called twice, so the environment is read more than once and a body that
|
|
;; consumed it would show.
|
|
(let [k 5]
|
|
(println (twice (fn [x] (+ x k)) 1)))
|
|
|
|
;; An fn's own let may shadow a name the enclosing function also has, and a
|
|
;; store into *that* one is an ordinary store: the refusal is about a
|
|
;; captured copy and not about the spelling. 5 + 1.
|
|
(let [n 5]
|
|
(println (+ n (call0 (fn [] (let [n 0] (set n 1) n))))))
|
|
|
|
(println (handles))
|
|
0)
|