flan/test/programs/generics.flan
Joseph Ferano f35af50f51 or-else is Java's, not Rust's, and the None it is tested against is reached
Four corrections to the prose and one to the test, none to the design.

The provenance line said "the name is Rust's or_else, the behaviour is
Rust's unwrap_or", which conflates two functions that differ in both
eagerness and return type — Rust's or_else takes a closure and answers
another Option.  Java's Optional.orElse is the exact match, and its lazy
sibling orElseGet is the one already declined a paragraph above.

The helper reaching or-else's None branch at an owning type asserted a
refusal nobody had run.  Compiled, it is "nothing here says what None is
an Option of — annotate the function's return type or the binding", so
the comment quotes that and the helper is a return type and nothing
else: its other branch was never called, in a program whose header says
every line is a claim.

read-file's comment claimed both restarts arrive unchanged and the test
runs use-value.  Narrowed to the mechanism (nothing here establishes a
handler) plus the half that is actually executed.

And edn-read.flan now says what becomes of its defn wrapper when the
computed-initialiser work lands, since that is the only thing keeping
the motivating line from being written as the defvar.
2026-09-19 04:30:57 +07:00

195 lines
7.4 KiB
Plaintext

;;;; Generics by monomorphisation, end to end.
;;;;
;;;; A [$t] binds a type variable in a defn signature and every call site
;;;; instantiates the body at the types it passes. The body is checked once
;;;; *abstractly*, with nothing substituted, so an operator the variable is
;;;; not declared to support is refused at the definition and not at whichever
;;;; call site happened to reach a type that worked — see generic-reject.flan
;;;; and generic-runaway.flan for that half.
;;;;
;;;; What this program is asserting, in order: one variable at several types,
;;;; a variable bound inside a slice, a generic calling a generic at its own
;;;; variable so that instantiation has to be transitive, the four where
;;;; predicates, two variables at once, println deferred to the instantiation,
;;;; the collapsed prelude family the whole feature was for, and the family
;;;; over (Option $t) — or-else and some? — which is the one that declares no
;;;; predicate at all, so a $t that owns storage instantiates it too.
;; One variable, several types, and (ident 3) and (ident 7) share one copy.
;; The identity needs its parameter once, so it needs nothing declared: a type
;; variable is move-only by default and one move is what this is.
(defn ident [x $t] $t x)
;; The variable is bound *inside* a type constructor, which is a structural
;; walk rather than a name match.
(defn first-or [s [$t] d $t] $t
(if (= (len s) 0) d (at s 0)))
;; A generic calling a generic at its own variable: the copy of [swap!] is
;; generated when [rotate!] is instantiated and not before.
(defn rotate! [s [$t]] ()
(dotimes [i (- (len s) 1)]
(swap! s i (+ i 1))))
;; numeric? admits + - * / %.
(defn twice [x $t] $t
{:where (numeric? $t)}
(+ x x))
;; equal? admits = and !=; ordered? admits < <= > >= min max, and entails
;; equal?.
(defn count-of [s [$t] x $t] i32
{:where (equal? $t)}
(let [n 0]
(dotimes [i (len s)]
(when (= (at s i) x)
(set n (+ n 1))))
n))
(defn clamp-to [x $t lo $t hi $t] $t
{:where (ordered? $t)}
(min (max x lo) hi))
;; Two variables, and the second is determined by its own argument.
(defn fst [a $t b $u] $t
(do b a))
;; println over a type variable is the one form the abstract pass defers to
;; the instantiation, because its legality is only decidable after
;; substituting. The structural printer is selected per copy.
(defn show [x $t] ()
(println x))
;; A cast to a type variable. [(t x)] is not a name [is_cast] knows — [t] is
;; not a machine type — so it is its own arm, and [numeric?] is what admits
;; it, because a cast produces a number. Inside the copy the target is
;; concrete and the emitter sees an ordinary cast.
(defn widen [x i32 d $t] $t
{:where (numeric? $t)}
(do d (t x)))
;; The builtins that take a *type name* as an argument, over a variable. Each
;; reaches the one list of what names a type; (map-new t i32) is the other.
(defn one-of [x $t] (Vec $t)
(let [v (vec-new t)]
(push v x)
v))
;; An empty (Option (Vec u8)), which main needs to reach or-else's None branch
;; at a type that owns storage. It is a function and not a bare None at the
;; call site because a bare None there is refused — "nothing here says what
;; None is an Option of" — and a return type is one of the two places the
;; checker names as somewhere to say it. It is also the shape every real caller
;; is in: what arrives at or-else came out of something, the way edn/read's
;; answer does.
(defn none-vec [] (Option (Vec u8))
None)
;; (zeroed) takes its type from the position it is written in, so a variable
;; in that position is answered by the instantiation like any other type.
(defn zero-of [x $t] $t
(do x (zeroed)))
;; The map operations over a key that is a type variable. The hash and the
;; equality are concrete symbols chosen from the concrete key type, so there
;; is nothing to emit here — these are deferred to the instantiation, the way
;; println is, and {:where (hashable? $t)} is what allows it: the refusal for
;; a key type that cannot be hashed lands at the call site, against a
;; requirement written down in this signature. Without the clause the type
;; (Map $t i32) is refused where it is written; see generic-map-reject.flan
;; for the call-site half.
(defn bump [k $t n i32] i32
{:where (hashable? $t)}
(let [m (map-new t i32)]
(reserve m 8)
(put m k n)
(put m k (+ n (match (get m k) (Some v) v _ 0)))
(let [c (clone m)
answer (+ (match (get c k) (Some v) v _ -1)
(if (has-key? c k) 1 0))]
(free c)
(free m)
answer)))
(defn main [] ()
(println (ident 3))
(println (ident 4.5))
(println (ident true))
(println (ident 7))
(let [ns [5 3 9 1]
fs [2.5 0.5 1.5]]
(println (first-or (slice ns 0 4) -1))
(println (first-or (slice ns 0 0) -1))
(rotate! (slice ns 0 4))
(println (at ns 3))
(println (twice 21))
(println (twice 1.5))
(println (count-of (slice ns 0 4) 9))
(println (clamp-to 12 0 10))
(println (clamp-to 0.5 1.0 9.0))
(println (fst 8 true))
(show 3)
(show 4.5)
(show "text")
;; The collapsed prelude family, at both element types.
(sort! (slice ns 0 4))
(println (at ns 0))
(sort-by! (slice fs 0 3) (fn [a b] (> a b)))
(println (at fs 0))
(reverse! (slice ns 0 4))
(println (at ns 0))
(map! (slice ns 0 4) (fn [x] (* x 2)))
(println (reduce (slice ns 0 4) 0 (fn [a b] (+ a b))))
(match (min-of (slice ns 0 4)) (Some m) (println m) _ (println -1))
(match (max-of (slice fs 0 3)) (Some m) (println m) _ (println -1.0))
(match (index-of (slice ns 0 4) 18) (Some i) (println i) _ (println -1))
;; or-else and some?, which are the same family over (Option $t) and take
;; no predicate: they move the payload out or read the tag, and neither is
;; an operation the variable has to be declared to support.
;;
;; Both branches at two scalar types, because a default that is returned
;; and a default that is discarded are two different lowerings and only one
;; of them is exercised by a call that happens to be Some.
(println (or-else (index-of (slice ns 0 4) 18) -1)) ; the Some branch
(println (or-else (index-of (slice ns 0 4) 77) -1)) ; the None branch
(println (or-else (max-of (slice fs 0 3)) 0.0))
(println (or-else (max-of (slice fs 0 0)) 0.0))
(println (some? (index-of (slice ns 0 4) 18)))
(println (some? (index-of (slice ns 0 4) 77)))
(println (some? (parse-i64 (bytes "12"))))
;; And at a $t that owns storage, which is the case the scalars above say
;; nothing about. What comes back is a *header* onto one of the two
;; buffers, so both are still the caller's to free — hence two frees and
;; not one, and the lengths are what say which header each answer holds.
(let [full (vec-new u8)
empty (vec-new u8)]
(push full 65)
(push full 66)
(println (len (or-else (Some full) empty))) ; 2, full's header
(println (len (or-else (none-vec) empty))) ; 0, empty's
(free full)
(free empty))
(println (widen 3 0.0))
(println (widen 3 (i64 0)))
(println (zero-of 9))
;; One written body, two key types, two emitted copies.
(println (bump 7 10))
(println (bump "key" 3))
(let [a (arena-new 4096)
keep (filter (slice ns 0 4) (fn [x] (> x 5)))
one (one-of 4.5)]
(println (len (as-slice keep)))
(println (at (as-slice one) 0))
(free one)
(free keep)
(free-all a))))