The syntax sketch shows syntax that compiles, and says where it does not

Every generic example in syntax-sketch.flan was written in the lowercase-variable
spelling that $t replaced, so the file whose job is to be copied from was the one
place the wrong spelling cost the most. Checking the replacements against the
compiler rather than against the sketch turned up three more, and each is now
either fixed or named:

- reduce is (reduce s init f) and its accumulator is the element type. The old
  argument order was Clojure's and the fold in centroid is not a shape it has.
- an fn cannot be written inline into a (Fn [$t $t] bool) parameter, because a
  generic body is checked with nothing substituted and the fn has no types to
  take. Inside a generic the callback is a named defn.
- (largest hps >) does not work and never did: an operator is not a name. Both
  this file and spec-memory.md asserted it.

centroid is left as it was, because component-wise arithmetic over a fixed array
is planned rather than built and an aspirational example is not a defect -- but
it now says so instead of leaving it to be found.
This commit is contained in:
Joseph Ferano 2026-09-14 07:32:03 +07:00
parent daee402026
commit d94e864dba

View File

@ -31,8 +31,8 @@
;; $t)}, is the other brace form, and it sits after the return type.
;; (Ptr World) pointer
;; (Fn [f32] bool) function pointer, no captured environment
;; (Option a) union from the stdlib
;; (Handle a) generational handle into a pool
;; (Option $t) union from the stdlib
;; (Handle $t) generational handle into a pool
;;
;; A struct is a value type iff all its fields are. One Vec field makes it
;; move-only. Copying an owning container is always explicit: (clone v).
@ -57,22 +57,51 @@
(Circle r) (* PI r r)
(Rect w h) (* w h)))
;; ── Lowercase = type variable. Monomorphised at each call site ────────
;; There are no type classes, so `a` supports only what EVERY type supports.
;; Ordering is not that — it is passed in as a function value. Type arguments
;; are inferred from the argument types; there is no explicit instantiation.
;; The inner `fn` captures `gt`, a parameter: legal because it does not outlive
;; this frame (spec-memory.md, non-escaping fn).
(defn largest [xs [a] gt (Fn [a a] bool)] (Option a)
;; ── $t binds a type variable. Monomorphised at each call site ─────────
;; The sigil is on the type, everywhere a type goes: [$t], (Fn [$t $t] bool),
;; (Option $t). Bare t is the same variable where a type's NAME is an argument
;; in expression position — (vec-new t), (map-new t i32), the cast (t x).
;; There are no type classes, so $t supports only what EVERY type supports, and
;; the body is checked abstractly, so an unsupported operation is an error here
;; rather than at the first call site that happened to instantiate it. Ordering
;; is not supported, so it is passed in as a function value. Type arguments are
;; inferred from the argument types; there is no explicit instantiation.
(defn largest [xs [$t] gt (Fn [$t $t] bool)] (Option $t)
{:where (copyable? $t)}
(if (> (len xs) 0)
(Some (reduce (fn [x y] (if (gt x y) x y)) (at xs 0) xs))
(let [best (at xs 0)]
(dotimes [i (len xs)]
(when (gt (at xs i) best) (set best (at xs i))))
(Some best))
None))
;; (largest hps >) — `>` at i32 is an ordinary function value
;; (largest es (fn [x y] (> (.hp x) (.hp y))))
;; A {:where ...} clause admits the operator instead of taking it as an
;; argument. Five predicates — ordered? equal? hashable? numeric? copyable? —
;; and each instantiation is checked against the ones the signature declares.
(defn smallest [xs [$t]] (Option $t)
{:where (ordered? $t)}
(if (> (len xs) 0)
(let [m (at xs 0)]
(dotimes [i (len xs)] (set m (min m (at xs i))))
(Some m))
None))
;; (largest hps taller) — a top-level defn is an ordinary function value.
;; An OPERATOR is not: `>` is not a name, and (largest hps >) is "unknown name
;; >". Nor can an `fn` be written inline into a (Fn [$t $t] bool) argument: the
;; generic body is checked with nothing substituted, so there is no type for the
;; fn's own parameters to come from yet. Inside a generic the callback is a
;; named defn; at a monomorphic call site the fn can be written where it is
;; used, as `centroid` does below.
;; Parameters are immutable values; pass a pointer to mutate. `[Enemy]` is a
;; borrowed slice — centroid neither owns nor frees the storage.
;; This one is still a sketch of where the syntax is going and does not compile
;; today, on two counts worth naming rather than leaving to be discovered:
;; component-wise `+` and `/` over a fixed array are planned and not built, and
;; the prelude's `reduce` is (reduce s init f) with its accumulator at the
;; ELEMENT type, so it cannot fold an [Enemy] into a Vec2. Written against what
;; exists, this is a `dotimes` accumulating into a local.
(defn centroid [es [Enemy]] Vec2
(/ (reduce (fn [acc e] (+ acc (.pos e))) [0 0] es)
(f32 (len es))))