flan/test/programs/fn-cfn.flan
Joseph Ferano 57fe91f303 Five records become one, and every citation lands somewhere
FIX.org, NEXT.md, DISCUSS.org, docs/DISCUSS.md and the session handoff at the
root are one TODO.org now: 293 entries under seven subsystem headings, each
carrying an org keyword that says where it stands. A DONE entry is a few lines
saying what was decided and what that rules out; the reasoning that would not
compress — the embedding spike and the four reports the hand-written x86
backend was built from — moved into docs/BUILT.md instead, and its entries
point there in one line.

Every entry was checked against the tree before it got a keyword, and the
prose was wrong in both directions. Things the deleted files called open were
built: the first-evaluation stall, main being redefinable, macro parameter
lists, the type-limit constants, the array constructors, the byte fills,
inc/dec, the discard's fontification, the Emacs buffers, rt_die's _exit, the
backtrace surface, and the acceptance failure that could print and still exit
zero. Things they called done were not: the backend reports' no-plan buckets
had gone stale in the other direction, the value-dependent defvar was
superseded rather than built, and macro-expansion source locations are on an
unmerged lane, so that entry is NEXT and names the branch.

Every comment that cited one of the five by name now cites a heading that
exists, in TODO.org or in docs/BUILT.md. The session reports under
docs/handoffs/ keep naming the files they worked on, because rewriting them
would falsify what those sessions did; each carries a note saying where the
content went.
2026-09-21 21:05:48 +07:00

68 lines
3.2 KiB
Plaintext

;; The narrow function type. A (CFn [T ...] R) is the bare code address —
;; one word, no environment, and therefore nothing that can capture. A
;; (Fn [T ...] R) is that address and the environment beside it, two words.
;;
;; The reason there are two rather than one: an environment on every signature
;; would tax every function in every program for a feature most of them never
;; use. With CFn written where it is wanted, an ordinary defn emits exactly
;; the signature it emitted before capture existed, and a call to it by name
;; is byte-for-byte what it was.
;;
;; The C is information and not decoration. A value with no environment is the
;; only kind that could ever cross to C, and under the --no-conditions
;; direction TODO.org, "CFn and C's calling convention", records — where a
;; signature that cannot transfer drops the channel too — one becomes literally
;; a C function pointer. It is not that today: a declare cannot take a function
;; type at all, and the refusal it meets says so. The name points at what the
;; type is, and at where it is going.
;;
;; **Nobody needs CFn.** An Fn accepts everything a CFn does, so the narrow
;; one is reached for on purpose, for one of four reasons: handing a function
;; to C, later; a table of bare addresses; forbidding capture at a boundary;
;; and the one that is likeliest in practice — a *named* function handed to an
;; Fn parameter goes through the widening thunk and pays an indirect hop per
;; call, where a CFn parameter is a direct call. (map-in-place s double) is
;; the example, and [apply-bare] below is it in miniature.
;;
;; Coercion is one-way. A defn's address and a non-capturing literal satisfy
;; both. An Fn does not narrow to a CFn — there is nowhere for the
;; environment to go — and fn-cfn-narrow.flan is that refusal.
(defn double [x i32] i32 (* x 2))
(defn negate [x i32] i32 (- 0 x))
;; Taking the narrow one. Nothing that reaches here can carry an environment,
;; which is what the signature is saying.
(defn apply-bare [f (CFn [i32] i32) x i32] i32 (f x))
;; And the wide one, which is what almost every higher-order signature wants.
(defn apply-any [f (Fn [i32] i32) x i32] i32 (f x))
;; A CFn returned. It is a link-time constant with nothing behind it, so
;; handing one back is no different from handing it down — which is exactly
;; what a capturing value cannot do.
(defn pick [up bool] (CFn [i32] i32) (if up double negate))
;; A CFn parameter widened to an Fn at a call: the address goes where an
;; environment would be and the thunk reads it back out. This is the hop the
;; narrow type exists to avoid.
(defn through [f (CFn [i32] i32) x i32] i32 (apply-any f x))
(defn main [] i32
;; A name into a CFn, and into an Fn.
(println (apply-bare double 4))
(println (apply-any negate 4))
;; A literal that captures nothing into a CFn.
(println (apply-bare (fn [x] (+ x 1)) 4))
;; And one that does capture, into an Fn.
(let [k 10]
(println (apply-any (fn [x] (+ x k)) 4)))
;; A returned CFn, called through a computed head.
(println ((pick true) 21))
(println ((pick false) 21))
;; The widening, twice over: a CFn local through a CFn parameter into
;; an Fn parameter.
(let [g double]
(println (through g 5)))
0)