The prelude's per-type families collapse: 22 functions become 10, 27 become 16
swap!, reverse!, sort!, sort-by!, index-of, min-of, max-of, map!, reduce and filter, each written once over $t. Every call site in the corpus moves with them. min-of and max-of are not min and max because min and max are builtins over two or more numbers and nothing shadows a builtin. These reduce a slice, which is a different operation at a different arity. sort-bytes! did not collapse into sort!, and the reason is the point of the predicates: a [u8] is not ordered? and cannot be, because < is an instruction and comparing two slices lexicographically is a loop. It is sort-by! with bytes<? written in, one line, keeping its name and its stability note. sum-i32/sum-f32 and append-i64!/append-f64! stay for the reasons the spike gave. Not what the notes predicted: none of the ten collapses on a signature change alone. filter and reduce need copyable? because the checker demands it - reduce's accumulator at (Vec i32) is a double move - and the rest declare it because a slice of owning elements would have them duplicating headers.
This commit is contained in:
parent
b438a71031
commit
dad725afe4
36
lib/check.ml
36
lib/check.ml
@ -4756,14 +4756,46 @@ and generic_call ctx ~want loc name vars pats pret args =
|
|||||||
vars;
|
vars;
|
||||||
let cparams = List.map (subst_ty !subst) pats in
|
let cparams = List.map (subst_ty !subst) pats in
|
||||||
let cret = subst_ty !subst pret in
|
let cret = subst_ty !subst pret in
|
||||||
if List.exists generic_ty cparams || generic_ty cret then
|
if List.exists generic_ty cparams || generic_ty cret then begin
|
||||||
(* One generic function calling another at its *own* variable, seen from
|
(* One generic function calling another at its *own* variable, seen from
|
||||||
the abstract pass over the caller's body — [sort-by!] calling [swap!]
|
the abstract pass over the caller's body — [sort-by!] calling [swap!]
|
||||||
at [t]. There is no copy to make yet: [t] is not a type. The node is
|
at [t]. There is no copy to make yet: [t] is not a type. The node is
|
||||||
built so the call still type-checks and is thrown away with the rest of
|
built so the call still type-checks and is thrown away with the rest of
|
||||||
the abstract pass; the real copy is generated when the caller is
|
the abstract pass; the real copy is generated when the caller is
|
||||||
instantiated and the same call site resolves [t] to a concrete type. *)
|
instantiated and the same call site resolves [t] to a concrete type.
|
||||||
|
|
||||||
|
But the callee's [where] clause is answerable here, and has to be. The
|
||||||
|
whole promise of the abstract pass is that a generic's refusals arrive
|
||||||
|
at its definition; if the predicate were left to the instantiation,
|
||||||
|
[(defn f [x $t] () (sort! [x]))] would be accepted at its definition
|
||||||
|
and refused at whichever call site first instantiated it — a refusal in
|
||||||
|
code the caller did not write, which is the thing the pass exists to
|
||||||
|
avoid. So the caller has to declare at least what the callee asks for,
|
||||||
|
and [pred_entails] means [ordered?] covers a callee wanting
|
||||||
|
[copyable?] without anyone writing both. *)
|
||||||
|
(match Hashtbl.find_opt ctx.env.generics name with
|
||||||
|
| None -> ()
|
||||||
|
| Some gfn ->
|
||||||
|
List.iter
|
||||||
|
(fun (p : Ast.pred) ->
|
||||||
|
match List.assoc_opt p.Ast.pvar !subst with
|
||||||
|
| Some (Types.Var v) when not (declares ctx.env.tvpreds v p.Ast.pname) ->
|
||||||
|
Loc.failk "check/predicate-not-carried" loc
|
||||||
|
"%s is written {:where (%s $%s)}, and this call passes the \
|
||||||
|
type variable %s, which nothing here declares %s. Add \
|
||||||
|
{:where (%s $%s)} to this function's own clause — a \
|
||||||
|
predicate a body relies on has to be carried by every \
|
||||||
|
signature between it and the call site"
|
||||||
|
name p.Ast.pname p.Ast.pvar v p.Ast.pname p.Ast.pname v
|
||||||
|
| Some t when not (generic_ty t) && not (pred_holds p.Ast.pname t) ->
|
||||||
|
Loc.failk "check/predicate-unsatisfied" loc
|
||||||
|
"%s is written {:where (%s $%s)}, and this call passes %s, \
|
||||||
|
which is not %s"
|
||||||
|
name p.Ast.pname p.Ast.pvar (Types.to_string t) p.Ast.pname
|
||||||
|
| _ -> ())
|
||||||
|
gfn.Ast.fwhere);
|
||||||
expect loc ~want (mk loc cret (Tast.Call (name, targs)))
|
expect loc ~want (mk loc cret (Tast.Call (name, targs)))
|
||||||
|
end
|
||||||
else
|
else
|
||||||
let sym = instantiate ctx.env loc name vars !subst cparams cret in
|
let sym = instantiate ctx.env loc name vars !subst cparams cret in
|
||||||
expect loc ~want (mk loc cret (Tast.Call (sym, targs)))
|
expect loc ~want (mk loc cret (Tast.Call (sym, targs)))
|
||||||
|
|||||||
349
lib/prelude.ml
349
lib/prelude.ml
@ -145,37 +145,109 @@ let source = {flan|
|
|||||||
;; shape and the same argument — so the set is the same i32 and f32 the rest of
|
;; shape and the same argument — so the set is the same i32 and f32 the rest of
|
||||||
;; this family covers.
|
;; this family covers.
|
||||||
|
|
||||||
(defn swap-i32! [s [i32] i i32 j i32] ()
|
;; ── One family, over one type variable ────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; What used to be a copy per element type. A [$t] binds a type variable in
|
||||||
|
;; the signature and every call site instantiates the body at the types it
|
||||||
|
;; passes, so [(sort! xs)] over a [i32] and over a [f32] are two emitted
|
||||||
|
;; bodies from one written one.
|
||||||
|
;;
|
||||||
|
;; **Two things in the signatures are not decoration.**
|
||||||
|
;;
|
||||||
|
;; [{:where (ordered? $t)}] is what lets the body write [<] at all. A type
|
||||||
|
;; variable supports only what it is declared to support — an unconstrained
|
||||||
|
;; one is refused at the *definition*, not at some later call site — and
|
||||||
|
;; [ordered?] is the predicate that admits [<], [<=], [>], [>=], [min] and
|
||||||
|
;; [max]. It admits [=] and [copyable?] too: every type the language orders is
|
||||||
|
;; a number or an enum, so it is equatable and it is not move-only.
|
||||||
|
;;
|
||||||
|
;; [{:where (copyable? $t)}] is the opt-out from the other default. A type
|
||||||
|
;; variable is **move-only** until it says otherwise, because move is the
|
||||||
|
;; stricter rule and assuming it can only refuse a valid program rather than
|
||||||
|
;; admit a broken one: [reduce]'s accumulator is read into [f] and then
|
||||||
|
;; assigned again, which is correct at [i32] and a double move at [(Vec i32)],
|
||||||
|
;; and the checker cannot tell which until it substitutes. So the ones that
|
||||||
|
;; hold an element in a local say [copyable?] and the ones that only move
|
||||||
|
;; elements between slots do not.
|
||||||
|
;;
|
||||||
|
;; **What did not collapse, and why it should not.** [sum-i32] and [sum-f32]
|
||||||
|
;; widen their element into [i64] and [f64]; "the wider type $t accumulates
|
||||||
|
;; into" is a type-level function, which is a constraint system of a different
|
||||||
|
;; kind, and a generic [sum] that took its accumulator and its [+] would just
|
||||||
|
;; be [reduce]. [append-i64!] and [append-f64!] are two different primitives.
|
||||||
|
;; [sort-bytes!] needs [bytes<?] rather than [<] — a [[u8]] is not [ordered?]
|
||||||
|
;; and cannot be — so it is [sort-by!] with the comparison written in, and it
|
||||||
|
;; keeps its name because the stability contract in its comment is worth
|
||||||
|
;; keeping attached to something.
|
||||||
|
|
||||||
|
(defn swap! [s [$t] i i32 j i32] ()
|
||||||
|
{:where (copyable? $t)}
|
||||||
(let [t (at s i)]
|
(let [t (at s i)]
|
||||||
(set (at s i) (at s j))
|
(set (at s i) (at s j))
|
||||||
(set (at s j) t)))
|
(set (at s j) t)))
|
||||||
|
|
||||||
(defn reverse-i32! [s [i32]] ()
|
(defn reverse! [s [$t]] ()
|
||||||
|
{:where (copyable? $t)}
|
||||||
(let [i 0
|
(let [i 0
|
||||||
j (- (len s) 1)]
|
j (- (len s) 1)]
|
||||||
(while (< i j)
|
(while (< i j)
|
||||||
(swap-i32! s i j)
|
(swap! s i j)
|
||||||
(set i (+ i 1))
|
(set i (+ i 1))
|
||||||
(set j (- j 1)))))
|
(set j (- j 1)))))
|
||||||
|
|
||||||
;; Insertion sort: in place, no recursion, no auxiliary array and no
|
;; Insertion sort: in place, no recursion, no auxiliary array — quicksort
|
||||||
;; comparison function — quicksort would want a stack and mergesort a buffer,
|
;; would want a stack and mergesort a buffer, and neither exists. Ascending,
|
||||||
;; and neither exists. Ascending, and stable, though with no payload type to
|
;; and stable, though with no payload type to carry that is not yet
|
||||||
;; carry that is not yet observable.
|
;; observable.
|
||||||
(defn sort-i32! [s [i32]] ()
|
;;
|
||||||
|
;; One caveat that only arises at f32: **a NaN in the input makes the order
|
||||||
|
;; undefined.** Every comparison against a NaN is false, so the insertion loop
|
||||||
|
;; never moves one and never moves anything past one; what comes out is sorted
|
||||||
|
;; within each run between NaNs and not sorted across them. That is what C's
|
||||||
|
;; qsort with a naive comparator does too, and the only fix is not to have
|
||||||
|
;; NaNs in the array — there is no ordering of the reals a NaN sits anywhere
|
||||||
|
;; in.
|
||||||
|
(defn sort! [s [$t]] ()
|
||||||
|
{:where (ordered? $t)}
|
||||||
(let [i 1]
|
(let [i 1]
|
||||||
(while (< i (len s))
|
(while (< i (len s))
|
||||||
(let [j i]
|
(let [j i]
|
||||||
;; `and` short-circuits, which is load-bearing: at j = 0 the left test
|
;; `and` short-circuits, which is load-bearing: at j = 0 the left test
|
||||||
;; fails and (at s -1) is never evaluated, so this does not trap.
|
;; fails and (at s -1) is never evaluated, so this does not trap.
|
||||||
(while (and (> j 0) (> (at s (- j 1)) (at s j)))
|
(while (and (> j 0) (> (at s (- j 1)) (at s j)))
|
||||||
(swap-i32! s (- j 1) j)
|
(swap! s (- j 1) j)
|
||||||
|
(set j (- j 1))))
|
||||||
|
(set i (+ i 1)))))
|
||||||
|
|
||||||
|
;; The same insertion sort, with the one comparison it had written in replaced
|
||||||
|
;; by the one it is told. before? answers "does a come before b", so passing
|
||||||
|
;; (fn [a b] (< a b)) is ascending and reversing it is descending — and a
|
||||||
|
;; caller wanting a key rather than an order writes the comparison.
|
||||||
|
;;
|
||||||
|
;; It is stable exactly as sort! is: the loop stops the moment before? says
|
||||||
|
;; no, so equal elements never swap past each other. A before? that is not a
|
||||||
|
;; strict weak ordering — one answering true for both (a b) and (b a) — is the
|
||||||
|
;; caller's mistake and shows up as an order, not as a loop: the inner while
|
||||||
|
;; is bounded by j reaching 0 whatever the comparison says.
|
||||||
|
;;
|
||||||
|
;; This one needs no [ordered?]: the comparison it cannot have is the
|
||||||
|
;; comparison it is given. It is the shape every generic had to take before
|
||||||
|
;; predicates existed, and it stays because passing a comparison is a real
|
||||||
|
;; thing to want and not only a workaround.
|
||||||
|
(defn sort-by! [s [$t] before? (Fn [$t $t] bool)] ()
|
||||||
|
{:where (copyable? $t)}
|
||||||
|
(let [i 1]
|
||||||
|
(while (< i (len s))
|
||||||
|
(let [j i]
|
||||||
|
(while (and (> j 0) (before? (at s j) (at s (- j 1))))
|
||||||
|
(swap! s (- j 1) j)
|
||||||
(set j (- j 1))))
|
(set j (- j 1))))
|
||||||
(set i (+ i 1)))))
|
(set i (+ i 1)))))
|
||||||
|
|
||||||
;; The first index holding x. None rather than -1, because Option is what the
|
;; The first index holding x. None rather than -1, because Option is what the
|
||||||
;; language has and a sentinel index is the bug this avoids.
|
;; language has and a sentinel index is the bug this avoids.
|
||||||
(defn index-of-i32 [s [i32] x i32] (Option i32)
|
(defn index-of [s [$t] x $t] (Option i32)
|
||||||
|
{:where (equal? $t)}
|
||||||
(dotimes [i (len s)]
|
(dotimes [i (len s)]
|
||||||
(when (= (at s i) x)
|
(when (= (at s i) x)
|
||||||
(return (Some i))))
|
(return (Some i))))
|
||||||
@ -183,8 +255,16 @@ let source = {flan|
|
|||||||
|
|
||||||
;; None for an empty slice: there is no least i32 that is also an honest
|
;; None for an empty slice: there is no least i32 that is also an honest
|
||||||
;; answer, and returning one would be a value the caller cannot tell from a
|
;; answer, and returning one would be a value the caller cannot tell from a
|
||||||
;; real element.
|
;; real element. A NaN in the input is not special-cased and propagates the
|
||||||
(defn min-i32 [s [i32]] (Option i32)
|
;; way it does through the builtins — the comparison fails, so the running
|
||||||
|
;; value simply does not change.
|
||||||
|
;;
|
||||||
|
;; Named min-of rather than min because [min] and [max] are builtins over two
|
||||||
|
;; or more numbers, and a defn cannot shadow a builtin: nothing shadows [+]
|
||||||
|
;; either. These reduce a slice, which is a different operation with a
|
||||||
|
;; different arity, so the different name is honest rather than a workaround.
|
||||||
|
(defn min-of [s [$t]] (Option $t)
|
||||||
|
{:where (ordered? $t)}
|
||||||
(if (= (len s) 0)
|
(if (= (len s) 0)
|
||||||
None
|
None
|
||||||
(let [m (at s 0)]
|
(let [m (at s 0)]
|
||||||
@ -192,7 +272,8 @@ let source = {flan|
|
|||||||
(set m (min m (at s i))))
|
(set m (min m (at s i))))
|
||||||
(Some m))))
|
(Some m))))
|
||||||
|
|
||||||
(defn max-i32 [s [i32]] (Option i32)
|
(defn max-of [s [$t]] (Option $t)
|
||||||
|
{:where (ordered? $t)}
|
||||||
(if (= (len s) 0)
|
(if (= (len s) 0)
|
||||||
None
|
None
|
||||||
(let [m (at s 0)]
|
(let [m (at s 0)]
|
||||||
@ -200,6 +281,54 @@ let source = {flan|
|
|||||||
(set m (max m (at s i))))
|
(set m (max m (at s i))))
|
||||||
(Some m))))
|
(Some m))))
|
||||||
|
|
||||||
|
;; map! writes back into the slice it was handed, for the same reason sort!
|
||||||
|
;; does — a slice is non-owning, and transforming a thing you already own
|
||||||
|
;; should not allocate. A map that produces a *different* element type is not
|
||||||
|
;; here: it is two type variables and a second signature, and nothing has
|
||||||
|
;; wanted it.
|
||||||
|
(defn map! [s [$t] f (Fn [$t] $t)] ()
|
||||||
|
{:where (copyable? $t)}
|
||||||
|
(dotimes [i (len s)]
|
||||||
|
(set (at s i) (f (at s i)))))
|
||||||
|
|
||||||
|
;; The general fold, of which sum-i32 is the special case with the + written
|
||||||
|
;; in. The accumulator comes first in the step, which is the order that reads
|
||||||
|
;; as (f acc x) and the order Odin's slice.reduce uses.
|
||||||
|
(defn reduce [s [$t] init $t f (Fn [$t $t] $t)] $t
|
||||||
|
{:where (copyable? $t)}
|
||||||
|
(let [acc init]
|
||||||
|
(dotimes [i (len s)]
|
||||||
|
(set acc (f acc (at s i))))
|
||||||
|
acc))
|
||||||
|
|
||||||
|
;; A new Vec holding the elements the predicate kept, in the order they were
|
||||||
|
;; in. Owned by the caller: (free v), or let a (free-all a) take the region.
|
||||||
|
;;
|
||||||
|
;; This is the one that proves the containers and the generics compose. It
|
||||||
|
;; allocates — (vec-new t), push, returns (Vec t) — and the type-erased Vec
|
||||||
|
;; runtime needed no change at all, because SizeOf and AlignOf are computed at
|
||||||
|
;; the instantiation site, where the element type is concrete.
|
||||||
|
(defn filter [s [$t] keep? (Fn [$t] bool)] (Vec $t)
|
||||||
|
{:where (copyable? $t)}
|
||||||
|
(let [v (vec-new t)]
|
||||||
|
(dotimes [i (len s)]
|
||||||
|
(when (keep? (at s i))
|
||||||
|
(push v (at s i))))
|
||||||
|
v))
|
||||||
|
|
||||||
|
;; ── The per-type layer that stays ─────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; sum is the one shape a type variable cannot express, and it is worth being
|
||||||
|
;; precise about why rather than leaving two near-identical functions looking
|
||||||
|
;; like an oversight. Each of these *widens*: sum-i32 accumulates in i64 and
|
||||||
|
;; sum-f32 in f64, with an explicit cast per element, because there is no
|
||||||
|
;; implicit widening anywhere in the language and summing a screenful into the
|
||||||
|
;; element's own type is how a total silently wraps or absorbs. "The wider
|
||||||
|
;; type $t accumulates into" is a function from types to types — an associated
|
||||||
|
;; type, or a constraint system of a kind {:where} is not — and a generic sum
|
||||||
|
;; that took its accumulator and its + as parameters would be reduce, which is
|
||||||
|
;; above.
|
||||||
|
|
||||||
;; Accumulates in i64 and each element is widened explicitly — there is no
|
;; Accumulates in i64 and each element is widened explicitly — there is no
|
||||||
;; implicit widening anywhere in the language, and summing a screenful of i32
|
;; implicit widening anywhere in the language, and summing a screenful of i32
|
||||||
;; into an i32 is how a total silently wraps.
|
;; into an i32 is how a total silently wraps.
|
||||||
@ -209,167 +338,19 @@ let source = {flan|
|
|||||||
(set t (+ t (i64 (at s i)))))
|
(set t (+ t (i64 (at s i)))))
|
||||||
t))
|
t))
|
||||||
|
|
||||||
;; ── The same family over f32 ──────────────────────────────────────────
|
|
||||||
;;
|
|
||||||
;; sort-i32! was the only sort in the language, which is what NEXT.md's second
|
|
||||||
;; tier means by "a sort that is not integers-only". This is the second, and it
|
|
||||||
;; is a copy and not an abstraction — see the note above on why.
|
|
||||||
;;
|
|
||||||
;; One caveat that has no counterpart in the i32 family, because it cannot
|
|
||||||
;; arise there: **a NaN in the input makes the order undefined.** Every
|
|
||||||
;; comparison against a NaN is false, so the insertion loop never moves one and
|
|
||||||
;; never moves anything past one; what comes out is sorted within each run
|
|
||||||
;; between NaNs and not sorted across them. That is what C's qsort with a naive
|
|
||||||
;; comparator does too. The fix is not to have NaNs in the array — which is
|
|
||||||
;; also the only fix, since there is no ordering of the reals that a NaN sits
|
|
||||||
;; anywhere in.
|
|
||||||
|
|
||||||
(defn swap-f32! [s [f32] i i32 j i32] ()
|
|
||||||
(let [t (at s i)]
|
|
||||||
(set (at s i) (at s j))
|
|
||||||
(set (at s j) t)))
|
|
||||||
|
|
||||||
(defn reverse-f32! [s [f32]] ()
|
|
||||||
(let [i 0
|
|
||||||
j (- (len s) 1)]
|
|
||||||
(while (< i j)
|
|
||||||
(swap-f32! s i j)
|
|
||||||
(set i (+ i 1))
|
|
||||||
(set j (- j 1)))))
|
|
||||||
|
|
||||||
(defn sort-f32! [s [f32]] ()
|
|
||||||
(let [i 1]
|
|
||||||
(while (< i (len s))
|
|
||||||
(let [j i]
|
|
||||||
(while (and (> j 0) (> (at s (- j 1)) (at s j)))
|
|
||||||
(swap-f32! s (- j 1) j)
|
|
||||||
(set j (- j 1))))
|
|
||||||
(set i (+ i 1)))))
|
|
||||||
|
|
||||||
;; None for an empty slice, exactly as min-i32 does. A NaN in the input is not
|
|
||||||
;; special-cased and propagates the same way it does through the builtins: the
|
|
||||||
;; comparison fails, so the running value simply does not change.
|
|
||||||
(defn min-f32 [s [f32]] (Option f32)
|
|
||||||
(if (= (len s) 0)
|
|
||||||
None
|
|
||||||
(let [m (at s 0)]
|
|
||||||
(dotimes [i (len s)]
|
|
||||||
(set m (min m (at s i))))
|
|
||||||
(Some m))))
|
|
||||||
|
|
||||||
(defn max-f32 [s [f32]] (Option f32)
|
|
||||||
(if (= (len s) 0)
|
|
||||||
None
|
|
||||||
(let [m (at s 0)]
|
|
||||||
(dotimes [i (len s)]
|
|
||||||
(set m (max m (at s i))))
|
|
||||||
(Some m))))
|
|
||||||
|
|
||||||
;; Accumulates in f64 and widens each element explicitly, which is sum-i32's
|
;; Accumulates in f64 and widens each element explicitly, which is sum-i32's
|
||||||
;; argument in its floating form and a stronger one: summing a screenful of f32
|
;; argument in its floating form and a stronger one: summing a screenful of
|
||||||
;; in f32 does not wrap, it *absorbs* — once the running total is large enough,
|
;; f32 in f32 does not wrap, it *absorbs* — once the running total is large
|
||||||
;; adding a small element rounds to no change at all, and the answer is silently
|
;; enough, adding a small element rounds to no change at all, and the answer
|
||||||
;; short rather than obviously wrong. An f64 accumulator has 29 more bits of
|
;; is silently short rather than obviously wrong. An f64 accumulator has 29
|
||||||
;; mantissa and pushes that failure out of reach of any array a game holds.
|
;; more bits of mantissa and pushes that failure out of reach of any array a
|
||||||
|
;; game holds.
|
||||||
(defn sum-f32 [s [f32]] f64
|
(defn sum-f32 [s [f32]] f64
|
||||||
(let [t 0.0]
|
(let [t 0.0]
|
||||||
(dotimes [i (len s)]
|
(dotimes [i (len s)]
|
||||||
(set t (+ t (f64 (at s i)))))
|
(set t (+ t (f64 (at s i)))))
|
||||||
t))
|
t))
|
||||||
|
|
||||||
;; ── The ones that take a function ─────────────────────────────────────
|
|
||||||
;;
|
|
||||||
;; map, filter, reduce and a comparator sort, which were the four the previous
|
|
||||||
;; tier could not write. The blocker was function values and not generics, and
|
|
||||||
;; the difference shows in what arrived and what did not: these take a
|
|
||||||
;; (Fn [T ...] R) as an ordinary parameter and needed nothing else, and they
|
|
||||||
;; are still one copy per element type because *that* is the generics half.
|
|
||||||
;;
|
|
||||||
;; Two rules, both inherited rather than invented here:
|
|
||||||
;;
|
|
||||||
;; 1. **The in-place ones stay in place.** map! writes back into the slice it
|
|
||||||
;; was handed, for the same reason sort-i32! does — a slice is non-owning,
|
|
||||||
;; and transforming a thing you already own should not allocate. A map that
|
|
||||||
;; produces a *different* element type is not here: it would be one copy per
|
|
||||||
;; ordered pair of types, which is the point at which a per-type family
|
|
||||||
;; stops being honest.
|
|
||||||
;; 2. **filter allocates and the caller frees**, like everything in the
|
|
||||||
;; building tier: (free v), or let a (free-all a) take the region.
|
|
||||||
;;
|
|
||||||
;; The function is passed by name — this is a Lisp-1, so a bare defn name is
|
|
||||||
;; the function — or written inline as an (fn [x] ...), whose parameter types
|
|
||||||
;; come from the parameter it is being passed to. It may not capture: an fn is
|
|
||||||
;; lifted into a function of its own and sees its parameters and the globals
|
|
||||||
;; and nothing else.
|
|
||||||
|
|
||||||
(defn map-i32! [s [i32] f (Fn [i32] i32)] ()
|
|
||||||
(dotimes [i (len s)]
|
|
||||||
(set (at s i) (f (at s i)))))
|
|
||||||
|
|
||||||
(defn map-f32! [s [f32] f (Fn [f32] f32)] ()
|
|
||||||
(dotimes [i (len s)]
|
|
||||||
(set (at s i) (f (at s i)))))
|
|
||||||
|
|
||||||
;; The general fold, of which sum-i32 is the special case with the + written
|
|
||||||
;; in. The accumulator comes first in the step, which is the order that reads
|
|
||||||
;; as (f acc x) and the order Odin's slice.reduce uses.
|
|
||||||
(defn reduce-i32 [s [i32] init i32 f (Fn [i32 i32] i32)] i32
|
|
||||||
(let [acc init]
|
|
||||||
(dotimes [i (len s)]
|
|
||||||
(set acc (f acc (at s i))))
|
|
||||||
acc))
|
|
||||||
|
|
||||||
(defn reduce-f32 [s [f32] init f32 f (Fn [f32 f32] f32)] f32
|
|
||||||
(let [acc init]
|
|
||||||
(dotimes [i (len s)]
|
|
||||||
(set acc (f acc (at s i))))
|
|
||||||
acc))
|
|
||||||
|
|
||||||
;; A new Vec holding the elements the predicate kept, in the order they were
|
|
||||||
;; in. Owned by the caller.
|
|
||||||
(defn filter-i32 [s [i32] keep? (Fn [i32] bool)] (Vec i32)
|
|
||||||
(let [v (vec-new i32)]
|
|
||||||
(dotimes [i (len s)]
|
|
||||||
(when (keep? (at s i))
|
|
||||||
(push v (at s i))))
|
|
||||||
v))
|
|
||||||
|
|
||||||
(defn filter-f32 [s [f32] keep? (Fn [f32] bool)] (Vec f32)
|
|
||||||
(let [v (vec-new f32)]
|
|
||||||
(dotimes [i (len s)]
|
|
||||||
(when (keep? (at s i))
|
|
||||||
(push v (at s i))))
|
|
||||||
v))
|
|
||||||
|
|
||||||
;; The same insertion sort sort-i32! is, with the one comparison it had written
|
|
||||||
;; in replaced by the one it is told. before? answers "does a come before b",
|
|
||||||
;; so passing (fn [a b] (< a b)) is ascending and reversing it is descending —
|
|
||||||
;; and a caller wanting a key rather than an order writes the comparison.
|
|
||||||
;;
|
|
||||||
;; It is stable exactly as sort-i32! is: the loop stops the moment before? says
|
|
||||||
;; no, so equal elements never swap past each other. A before? that is not a
|
|
||||||
;; strict weak ordering — one answering true for both (a b) and (b a) — is the
|
|
||||||
;; caller's mistake and shows up as an order, not as a loop: the inner while is
|
|
||||||
;; bounded by j reaching 0 whatever the comparison says.
|
|
||||||
(defn sort-i32-by! [s [i32] before? (Fn [i32 i32] bool)] ()
|
|
||||||
(let [i 1]
|
|
||||||
(while (< i (len s))
|
|
||||||
(let [j i]
|
|
||||||
;; `and` short-circuits, so (at s -1) is never evaluated at j = 0.
|
|
||||||
(while (and (> j 0) (before? (at s j) (at s (- j 1))))
|
|
||||||
(swap-i32! s (- j 1) j)
|
|
||||||
(set j (- j 1))))
|
|
||||||
(set i (+ i 1)))))
|
|
||||||
|
|
||||||
(defn sort-f32-by! [s [f32] before? (Fn [f32 f32] bool)] ()
|
|
||||||
(let [i 1]
|
|
||||||
(while (< i (len s))
|
|
||||||
(let [j i]
|
|
||||||
(while (and (> j 0) (before? (at s j) (at s (- j 1))))
|
|
||||||
(swap-f32! s (- j 1) j)
|
|
||||||
(set j (- j 1))))
|
|
||||||
(set i (+ i 1)))))
|
|
||||||
|
|
||||||
;; ── Bytes ─────────────────────────────────────────────────────────────
|
;; ── Bytes ─────────────────────────────────────────────────────────────
|
||||||
;;
|
;;
|
||||||
;; Over [u8] and not over string, so (bytes s) is what a caller writes and one
|
;; Over [u8] and not over string, so (bytes s) is what a caller writes and one
|
||||||
@ -397,12 +378,6 @@ let source = {flan|
|
|||||||
(and (<= (len p) (len s))
|
(and (<= (len p) (len s))
|
||||||
(bytes=? (slice s (- (len s) (len p)) (len s)) p)))
|
(bytes=? (slice s (- (len s) (len p)) (len s)) p)))
|
||||||
|
|
||||||
(defn index-of-byte [s [u8] b u8] (Option i32)
|
|
||||||
(dotimes [i (len s)]
|
|
||||||
(when (= (at s i) b)
|
|
||||||
(return (Some i))))
|
|
||||||
None)
|
|
||||||
|
|
||||||
;; The whole slice is an integer, or it is None. bytes->i64 is strtoll, which
|
;; The whole slice is an integer, or it is None. bytes->i64 is strtoll, which
|
||||||
;; answers 0 for "" and for "abc" and stops at the first junk byte in "12x" —
|
;; answers 0 for "" and for "abc" and stops at the first junk byte in "12x" —
|
||||||
;; three wrong answers a caller cannot tell from a real 12. This is also the
|
;; three wrong answers a caller cannot tell from a real 12. This is also the
|
||||||
@ -868,7 +843,7 @@ let source = {flan|
|
|||||||
|
|
||||||
;; How many bytes this code point encodes to, or None if it is not a scalar
|
;; How many bytes this code point encodes to, or None if it is not a scalar
|
||||||
;; value. Odin's rune_size answers -1 for the refusals; a sentinel index is
|
;; value. Odin's rune_size answers -1 for the refusals; a sentinel index is
|
||||||
;; exactly what index-of-i32 avoids above, so this is an Option like the rest
|
;; exactly what index-of avoids above, so this is an Option like the rest
|
||||||
;; of the file.
|
;; of the file.
|
||||||
(defn rune-size [code i32] (Option i32)
|
(defn rune-size [code i32] (Option i32)
|
||||||
(cond
|
(cond
|
||||||
@ -944,7 +919,7 @@ let source = {flan|
|
|||||||
(defn split-next! [it (Ptr Split)] (Option [u8])
|
(defn split-next! [it (Ptr Split)] (Option [u8])
|
||||||
(when (not (.more it))
|
(when (not (.more it))
|
||||||
(return None))
|
(return None))
|
||||||
(match (index-of-byte (.rest it) (.sep it))
|
(match (index-of (.rest it) (.sep it))
|
||||||
(Some i)
|
(Some i)
|
||||||
(let [field (slice (.rest it) 0 i)]
|
(let [field (slice (.rest it) 0 i)]
|
||||||
(set (.rest it) (slice (.rest it) (+ i 1) (len (.rest it))))
|
(set (.rest it) (slice (.rest it) (+ i 1) (len (.rest it))))
|
||||||
@ -1030,24 +1005,20 @@ let source = {flan|
|
|||||||
(return (< (at a i) (at b i)))))
|
(return (< (at a i) (at b i)))))
|
||||||
(< (len a) (len b))))
|
(< (len a) (len b))))
|
||||||
|
|
||||||
(defn swap-bytes! [s [[u8]] i i32 j i32] ()
|
;; sort-by! with the comparison written in, over the same in-place contract:
|
||||||
(let [t (at s i)]
|
;; the *slices* move, never the bytes they point at, so this sorts a [[u8]] of
|
||||||
(set (at s i) (at s j))
|
|
||||||
(set (at s j) t)))
|
|
||||||
|
|
||||||
;; The same insertion sort as sort-i32!, over the same in-place contract: the
|
|
||||||
;; *slices* move, never the bytes they point at, so this sorts a [[u8]] of
|
|
||||||
;; fields borrowed from one buffer without touching the buffer. Stable, and
|
;; fields borrowed from one buffer without touching the buffer. Stable, and
|
||||||
;; here that is observable — two equal fields are two distinct slices of
|
;; here that is observable — two equal fields are two distinct slices of
|
||||||
;; different parts of the input, and a caller can see which one came first.
|
;; different parts of the input, and a caller can see which one came first.
|
||||||
|
;;
|
||||||
|
;; It keeps a name of its own rather than collapsing into sort!, and the
|
||||||
|
;; reason is the point of the predicates: a [u8] is not ordered? and cannot
|
||||||
|
;; be, because < is defined on machine numbers and comparing two slices
|
||||||
|
;; lexicographically is a loop and not an instruction. bytes<? is that loop.
|
||||||
|
;; So this is the shape a generic takes when the operation it needs is not a
|
||||||
|
;; primitive: pass it in.
|
||||||
(defn sort-bytes! [s [[u8]]] ()
|
(defn sort-bytes! [s [[u8]]] ()
|
||||||
(let [i 1]
|
(sort-by! s (fn [a b] (bytes<? a b))))
|
||||||
(while (< i (len s))
|
|
||||||
(let [j i]
|
|
||||||
(while (and (> j 0) (bytes<? (at s j) (at s (- j 1))))
|
|
||||||
(swap-bytes! s (- j 1) j)
|
|
||||||
(set j (- j 1))))
|
|
||||||
(set i (+ i 1)))))
|
|
||||||
|
|
||||||
;; ── Building bytes, which is the tier that needed an allocator ────────
|
;; ── Building bytes, which is the tier that needed an allocator ────────
|
||||||
;;
|
;;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
;;;; The slice family at its second and third element types.
|
;;;; The slice family at its second and third element types.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; sort-i32! was the only sort in the language. These are the other two, and
|
;;;; sort! was the only sort in the language. These are the other two, and
|
||||||
;;;; they are copies rather than an abstraction: map, filter, reduce and a sort
|
;;;; they are copies rather than an abstraction: map, filter, reduce and a sort
|
||||||
;;;; taking a comparator all need a *function value*, which check.ml refuses
|
;;;; taking a comparator all need a *function value*, which check.ml refuses
|
||||||
;;;; with "a function type is not implemented yet -- milestone 5". So the
|
;;;; with "a function type is not implemented yet -- milestone 5". So the
|
||||||
@ -22,55 +22,55 @@
|
|||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
;; Every float literal is cast. A literal defaults to f64 and an array
|
;; Every float literal is cast. A literal defaults to f64 and an array
|
||||||
;; literal has no context to say otherwise -- a let has no type annotation --
|
;; literal has no context to say otherwise -- a let has no type annotation --
|
||||||
;; so [3.5 -1.0] is an [f64] and (sort-f32!) refuses it by type. The cast is
|
;; so [3.5 -1.0] is an [f64] and (sort!) refuses it by type. The cast is
|
||||||
;; the only spelling available today.
|
;; the only spelling available today.
|
||||||
;;
|
;;
|
||||||
;; sort-f32!: duplicates, negatives, a zero and an odd length, which is the
|
;; sort!: duplicates, negatives, a zero and an odd length, which is the
|
||||||
;; input shape the i32 sort is tested on for the same reasons.
|
;; input shape the i32 sort is tested on for the same reasons.
|
||||||
(let [xs [(f32 3.5) (f32 -1.0) (f32 0.0) (f32 3.5) (f32 -2.25) (f32 10.0) (f32 0.5)]]
|
(let [xs [(f32 3.5) (f32 -1.0) (f32 0.0) (f32 3.5) (f32 -2.25) (f32 10.0) (f32 0.5)]]
|
||||||
(sort-f32! (slice xs 0 7))
|
(sort! (slice xs 0 7))
|
||||||
(show-f32 (slice xs 0 7))) ; -2.25 -1 0 0.5 3.5 3.5 10
|
(show-f32 (slice xs 0 7))) ; -2.25 -1 0 0.5 3.5 3.5 10
|
||||||
|
|
||||||
;; In place and ptr+len: sorting a subslice leaves its neighbours alone. That
|
;; In place and ptr+len: sorting a subslice leaves its neighbours alone. That
|
||||||
;; is the whole content of the in-place claim, and a version that copied
|
;; is the whole content of the in-place claim, and a version that copied
|
||||||
;; would pass every test above and fail this one.
|
;; would pass every test above and fail this one.
|
||||||
(let [xs [(f32 9.0) (f32 4.0) (f32 3.0) (f32 2.0) (f32 1.0) (f32 9.0)]]
|
(let [xs [(f32 9.0) (f32 4.0) (f32 3.0) (f32 2.0) (f32 1.0) (f32 9.0)]]
|
||||||
(sort-f32! (slice xs 1 5))
|
(sort! (slice xs 1 5))
|
||||||
(show-f32 (slice xs 0 6))) ; 9 1 2 3 4 9
|
(show-f32 (slice xs 0 6))) ; 9 1 2 3 4 9
|
||||||
|
|
||||||
;; Already sorted, reverse sorted, and a single element -- the three inputs
|
;; Already sorted, reverse sorted, and a single element -- the three inputs
|
||||||
;; where an insertion loop with the comparison the wrong way round still
|
;; where an insertion loop with the comparison the wrong way round still
|
||||||
;; looks plausible.
|
;; looks plausible.
|
||||||
(let [xs [(f32 1.0) (f32 2.0) (f32 3.0)]]
|
(let [xs [(f32 1.0) (f32 2.0) (f32 3.0)]]
|
||||||
(sort-f32! (slice xs 0 3))
|
(sort! (slice xs 0 3))
|
||||||
(show-f32 (slice xs 0 3))) ; 1 2 3
|
(show-f32 (slice xs 0 3))) ; 1 2 3
|
||||||
(let [xs [(f32 3.0) (f32 2.0) (f32 1.0)]]
|
(let [xs [(f32 3.0) (f32 2.0) (f32 1.0)]]
|
||||||
(sort-f32! (slice xs 0 3))
|
(sort! (slice xs 0 3))
|
||||||
(show-f32 (slice xs 0 3))) ; 1 2 3
|
(show-f32 (slice xs 0 3))) ; 1 2 3
|
||||||
(let [xs [(f32 7.0)]]
|
(let [xs [(f32 7.0)]]
|
||||||
(sort-f32! (slice xs 0 1))
|
(sort! (slice xs 0 1))
|
||||||
(show-f32 (slice xs 0 1))) ; 7
|
(show-f32 (slice xs 0 1))) ; 7
|
||||||
;; The empty slice must not read (at s -1).
|
;; The empty slice must not read (at s -1).
|
||||||
(let [xs [(f32 7.0)]]
|
(let [xs [(f32 7.0)]]
|
||||||
(sort-f32! (slice xs 0 0))
|
(sort! (slice xs 0 0))
|
||||||
(show-f32 (slice xs 0 0))) ;
|
(show-f32 (slice xs 0 0))) ;
|
||||||
|
|
||||||
(let [xs [(f32 1.0) (f32 2.0) (f32 3.0) (f32 4.0)]]
|
(let [xs [(f32 1.0) (f32 2.0) (f32 3.0) (f32 4.0)]]
|
||||||
(reverse-f32! (slice xs 0 4))
|
(reverse! (slice xs 0 4))
|
||||||
(show-f32 (slice xs 0 4))) ; 4 3 2 1
|
(show-f32 (slice xs 0 4))) ; 4 3 2 1
|
||||||
|
|
||||||
;; min, max and sum. The empty slice is None for the first two -- there is no
|
;; min, max and sum. The empty slice is None for the first two -- there is no
|
||||||
;; least f32 that is also an honest answer -- and the sum accumulates in f64,
|
;; least f32 that is also an honest answer -- and the sum accumulates in f64,
|
||||||
;; which is why 16777216 + 1 does not absorb here the way it would in f32.
|
;; which is why 16777216 + 1 does not absorb here the way it would in f32.
|
||||||
(let [xs [(f32 3.5) (f32 -1.0) (f32 10.0)]]
|
(let [xs [(f32 3.5) (f32 -1.0) (f32 10.0)]]
|
||||||
(match (min-f32 (slice xs 0 3)) (Some m) (print m) None (print "none"))
|
(match (min-of (slice xs 0 3)) (Some m) (print m) None (print "none"))
|
||||||
(print " ")
|
(print " ")
|
||||||
(match (max-f32 (slice xs 0 3)) (Some m) (print m) None (print "none"))
|
(match (max-of (slice xs 0 3)) (Some m) (print m) None (print "none"))
|
||||||
(print " ")
|
(print " ")
|
||||||
(print (sum-f32 (slice xs 0 3)))
|
(print (sum-f32 (slice xs 0 3)))
|
||||||
(println "")) ; -1 10 12.5
|
(println "")) ; -1 10 12.5
|
||||||
(let [xs [(f32 1.0)]]
|
(let [xs [(f32 1.0)]]
|
||||||
(match (min-f32 (slice xs 0 0)) (Some m) (print m) None (print "none"))
|
(match (min-of (slice xs 0 0)) (Some m) (print m) None (print "none"))
|
||||||
(print " ")
|
(print " ")
|
||||||
(print (sum-f32 (slice xs 0 0)))
|
(print (sum-f32 (slice xs 0 0)))
|
||||||
(println "")) ; none 0
|
(println "")) ; none 0
|
||||||
|
|||||||
@ -23,11 +23,11 @@
|
|||||||
;; A comparator, which is the other half of what was blocked: a sort that is
|
;; A comparator, which is the other half of what was blocked: a sort that is
|
||||||
;; told the order rather than having it written in. Insertion sort, because the
|
;; told the order rather than having it written in. Insertion sort, because the
|
||||||
;; point here is the parameter and not the algorithm.
|
;; point here is the parameter and not the algorithm.
|
||||||
(defn sort-by! [xs [i32] before? (Fn [i32 i32] bool)] ()
|
(defn insertion-by! [xs [i32] before? (Fn [i32 i32] bool)] ()
|
||||||
(dotimes [i (len xs)]
|
(dotimes [i (len xs)]
|
||||||
(let [j i]
|
(let [j i]
|
||||||
(while (and (> j 0) (before? (at xs j) (at xs (- j 1))))
|
(while (and (> j 0) (before? (at xs j) (at xs (- j 1))))
|
||||||
(swap-i32! xs j (- j 1))
|
(swap! xs j (- j 1))
|
||||||
(set j (- j 1))))))
|
(set j (- j 1))))))
|
||||||
|
|
||||||
(defn ascending [a i32 b i32] bool (< a b))
|
(defn ascending [a i32 b i32] bool (< a b))
|
||||||
@ -75,12 +75,12 @@
|
|||||||
;; A comparator, and the same slice sorted both ways.
|
;; A comparator, and the same slice sorted both ways.
|
||||||
(let [ys [3 1 4 1 5 9 2 6]
|
(let [ys [3 1 4 1 5 9 2 6]
|
||||||
s (slice ys 0 8)]
|
s (slice ys 0 8)]
|
||||||
(sort-by! s ascending)
|
(insertion-by! s ascending)
|
||||||
(print (at s 0)) (print " ") (print (at s 7)) (println "")
|
(print (at s 0)) (print " ") (print (at s 7)) (println "")
|
||||||
(sort-by! s descending)
|
(insertion-by! s descending)
|
||||||
(print (at s 0)) (print " ") (print (at s 7)) (println "")
|
(print (at s 0)) (print " ") (print (at s 7)) (println "")
|
||||||
;; A returned function value, and a computed head calling it.
|
;; A returned function value, and a computed head calling it.
|
||||||
(sort-by! s (pick true))
|
(insertion-by! s (pick true))
|
||||||
(print (at s 0)) (println "")
|
(print (at s 0)) (println "")
|
||||||
(println ((pick false) 1 2)))
|
(println ((pick false) 1 2)))
|
||||||
|
|
||||||
|
|||||||
15
test/programs/generic-reject.flan
Normal file
15
test/programs/generic-reject.flan
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
;;;; The refusal, at the definition and not at a call site.
|
||||||
|
;;;;
|
||||||
|
;;;; A generic body is checked once with its type variables abstract, so an
|
||||||
|
;;;; operator the variable is not declared to support is refused here, naming
|
||||||
|
;;;; the variable — rather than at whichever call site first instantiated it
|
||||||
|
;;;; at a type that did not work. That is not Odin's model: Odin checks a
|
||||||
|
;;;; polymorphic body only per instantiation, so (+ a b) over a $T compiles
|
||||||
|
;;;; there and fails only if someone reaches it at a type without +.
|
||||||
|
;;;;
|
||||||
|
;;;; The way out is either predicate — {:where (numeric? $t)} — or the
|
||||||
|
;;;; parameter, a (Fn [$t $t] $t) the caller supplies. Neither is written
|
||||||
|
;;;; here, which is the point.
|
||||||
|
(defn add2 [a $t b $t] $t (+ a b))
|
||||||
|
|
||||||
|
(defn main [] () (println (add2 1 2)))
|
||||||
10
test/programs/generic-runaway.flan
Normal file
10
test/programs/generic-runaway.flan
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
;;;; A generic that instantiates itself at a larger type every time.
|
||||||
|
;;;;
|
||||||
|
;;;; (grow [x x]) asks for a copy at [t], which asks for one at [[t]],
|
||||||
|
;;;; forever. Before the refusal this did not fail, it *hung*, and since
|
||||||
|
;;;; Session.eval runs the same code the thing that hung was C-c C-c with the
|
||||||
|
;;;; dev daemon wedged behind it. The refusal names the chain of
|
||||||
|
;;;; instantiations rather than a depth it gave up at.
|
||||||
|
(defn grow [x $t] () {:where (copyable? $t)} (grow [x x]))
|
||||||
|
|
||||||
|
(defn main [] () (grow 1))
|
||||||
105
test/programs/generics.flan
Normal file
105
test/programs/generics.flan
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
;;;; 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,
|
||||||
|
;;;; and the collapsed prelude family the whole feature was for.
|
||||||
|
|
||||||
|
;; 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
|
||||||
|
{:where (copyable? $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]] ()
|
||||||
|
{:where (copyable? $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? and copyable?.
|
||||||
|
(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
|
||||||
|
{:where [(copyable? $t) (copyable? $u)]}
|
||||||
|
(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] ()
|
||||||
|
{:where (copyable? $t)}
|
||||||
|
(println x))
|
||||||
|
|
||||||
|
(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))
|
||||||
|
(let [a (arena-new 4096)
|
||||||
|
keep (filter (slice ns 0 4) (fn [x] (> x 5)))]
|
||||||
|
(println (len (as-slice keep)))
|
||||||
|
(free keep)
|
||||||
|
(free-all a))))
|
||||||
@ -15,36 +15,36 @@
|
|||||||
;; map! writes back into the slice it was handed.
|
;; map! writes back into the slice it was handed.
|
||||||
(let [xs [1 2 3 4]
|
(let [xs [1 2 3 4]
|
||||||
s (slice xs 0 4)]
|
s (slice xs 0 4)]
|
||||||
(map-i32! s triple)
|
(map! s triple)
|
||||||
(print (at s 0)) (print " ") (print (at s 3)) (println "")
|
(print (at s 0)) (print " ") (print (at s 3)) (println "")
|
||||||
|
|
||||||
;; reduce, with the accumulator first in the step. The prelude's own
|
;; reduce, with the accumulator first in the step. The prelude's own
|
||||||
;; sum-i32 is this with the + written in.
|
;; sum-i32 is this with the + written in.
|
||||||
(print (reduce-i32 s 0 adds)) (println "")
|
(print (reduce s 0 adds)) (println "")
|
||||||
;; ... and an fn literal, whose parameter types come from the parameter.
|
;; ... and an fn literal, whose parameter types come from the parameter.
|
||||||
(print (reduce-i32 s 1 (fn [a b] (* a b)))) (println "")
|
(print (reduce s 1 (fn [a b] (* a b)))) (println "")
|
||||||
|
|
||||||
;; filter allocates and the caller frees.
|
;; filter allocates and the caller frees.
|
||||||
(let [v (filter-i32 s odd?)]
|
(let [v (filter s odd?)]
|
||||||
(print (len v)) (print " ") (print (at v 0)) (println "")
|
(print (len v)) (print " ") (print (at v 0)) (println "")
|
||||||
(free v))
|
(free v))
|
||||||
|
|
||||||
;; A comparator sort, both directions off the same slice.
|
;; A comparator sort, both directions off the same slice.
|
||||||
(sort-i32-by! s longer-first)
|
(sort-by! s longer-first)
|
||||||
(print (at s 0)) (print " ") (print (at s 3)) (println "")
|
(print (at s 0)) (print " ") (print (at s 3)) (println "")
|
||||||
(sort-i32-by! s (fn [a b] (< a b)))
|
(sort-by! s (fn [a b] (< a b)))
|
||||||
(print (at s 0)) (print " ") (print (at s 3)) (println ""))
|
(print (at s 0)) (print " ") (print (at s 3)) (println ""))
|
||||||
|
|
||||||
;; The f32 half of the family, which is the same code at the other element
|
;; The f32 half of the family, which is the same code at the other element
|
||||||
;; type — the copy that generics would remove.
|
;; type — the copy that generics would remove.
|
||||||
(let [ys [(f32 4.0) (f32 1.0) (f32 8.0) (f32 2.0)]
|
(let [ys [(f32 4.0) (f32 1.0) (f32 8.0) (f32 2.0)]
|
||||||
t (slice ys 0 4)]
|
t (slice ys 0 4)]
|
||||||
(map-f32! t halve)
|
(map! t halve)
|
||||||
(print (at t 0)) (print " ") (print (at t 2)) (println "")
|
(print (at t 0)) (print " ") (print (at t 2)) (println "")
|
||||||
(print (reduce-f32 t 0.0 (fn [a b] (+ a b)))) (println "")
|
(print (reduce t 0.0 (fn [a b] (+ a b)))) (println "")
|
||||||
(let [w (filter-f32 t big?)]
|
(let [w (filter t big?)]
|
||||||
(print (len w)) (println "")
|
(print (len w)) (println "")
|
||||||
(free w))
|
(free w))
|
||||||
(sort-f32-by! t (fn [a b] (> a b)))
|
(sort-by! t (fn [a b] (> a b)))
|
||||||
(print (at t 0)) (print " ") (print (at t 3)) (println ""))
|
(print (at t 0)) (print " ") (print (at t 3)) (println ""))
|
||||||
0)
|
0)
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
;;;; functions below is called from exactly one place, and that place is an
|
;;;; functions below is called from exactly one place, and that place is an
|
||||||
;;;; edge no other program in the corpus exercises:
|
;;;; edge no other program in the corpus exercises:
|
||||||
;;;;
|
;;;;
|
||||||
;;;; index-of the index expression of a place, (set (at a (f)) v)
|
;;;; index-expr the index expression of a place, (set (at a (f)) v)
|
||||||
;;;; through a place under (addr ...), here a (deref ...) so that it is
|
;;;; through a place under (addr ...), here a (deref ...) so that it is
|
||||||
;;;; the addr edge and not the index one again
|
;;;; the addr edge and not the index one again
|
||||||
;;;; placeholder a restart-case clause body, which is reached by a transfer
|
;;;; placeholder a restart-case clause body, which is reached by a transfer
|
||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
(defstruct Nope [id i32])
|
(defstruct Nope [id i32])
|
||||||
|
|
||||||
(defn index-of [] i32 2)
|
(defn index-expr [] i32 2)
|
||||||
|
|
||||||
(defn through [] (Ptr i32) (addr slot))
|
(defn through [] (Ptr i32) (addr slot))
|
||||||
|
|
||||||
@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
;; The index of a place is an expression, and it can call.
|
;; The index of a place is an expression, and it can call.
|
||||||
(set (at cells (index-of)) 10)
|
(set (at cells (index-expr)) 10)
|
||||||
(print (at cells 2)) (println "")
|
(print (at cells 2)) (println "")
|
||||||
|
|
||||||
;; (addr (deref p)) is p, so this is the addr edge over a place whose own
|
;; (addr (deref p)) is p, so this is the addr edge over a place whose own
|
||||||
|
|||||||
@ -36,47 +36,47 @@
|
|||||||
|
|
||||||
;; Reading the whole slice, before anything reorders it.
|
;; Reading the whole slice, before anything reorders it.
|
||||||
(print (sum-i32 (slice xs 0 (len xs)))) (println "") ; 23
|
(print (sum-i32 (slice xs 0 (len xs)))) (println "") ; 23
|
||||||
(print (match (min-i32 (slice xs 0 (len xs))) (Some v) v None 99))
|
(print (match (min-of (slice xs 0 (len xs))) (Some v) v None 99))
|
||||||
(println "") ; -3
|
(println "") ; -3
|
||||||
(print (match (max-i32 (slice xs 0 (len xs))) (Some v) v None 99))
|
(print (match (max-of (slice xs 0 (len xs))) (Some v) v None 99))
|
||||||
(println "") ; 12
|
(println "") ; 12
|
||||||
;; First index, not the last: 5 appears at 0 and at 2.
|
;; First index, not the last: 5 appears at 0 and at 2.
|
||||||
(print (match (index-of-i32 (slice xs 0 (len xs)) 5) (Some v) v None -1))
|
(print (match (index-of (slice xs 0 (len xs)) 5) (Some v) v None -1))
|
||||||
(println "") ; 0
|
(println "") ; 0
|
||||||
(print (match (index-of-i32 (slice xs 0 (len xs)) 4) (Some v) v None -1))
|
(print (match (index-of (slice xs 0 (len xs)) 4) (Some v) v None -1))
|
||||||
(println "") ; -1
|
(println "") ; -1
|
||||||
;; An empty slice has no least element, and None is the answer.
|
;; An empty slice has no least element, and None is the answer.
|
||||||
(print (match (min-i32 (slice xs 3 3)) (Some v) v None 99))
|
(print (match (min-of (slice xs 3 3)) (Some v) v None 99))
|
||||||
(println "") ; 99
|
(println "") ; 99
|
||||||
|
|
||||||
;; Reverse of an odd-length slice: the middle element stays put.
|
;; Reverse of an odd-length slice: the middle element stays put.
|
||||||
(reverse-i32! (slice xs 0 (len xs)))
|
(reverse! (slice xs 0 (len xs)))
|
||||||
(show (slice xs 0 (len xs))) ; 7 -3 12 0 5 -3 5
|
(show (slice xs 0 (len xs))) ; 7 -3 12 0 5 -3 5
|
||||||
;; And of a two-element one, the smallest case that can actually move.
|
;; And of a two-element one, the smallest case that can actually move.
|
||||||
(reverse-i32! (slice xs 0 2))
|
(reverse! (slice xs 0 2))
|
||||||
(show (slice xs 0 (len xs))) ; -3 7 12 0 5 -3 5
|
(show (slice xs 0 (len xs))) ; -3 7 12 0 5 -3 5
|
||||||
|
|
||||||
(load-xs)
|
(load-xs)
|
||||||
(sort-i32! (slice xs 0 (len xs)))
|
(sort! (slice xs 0 (len xs)))
|
||||||
(show (slice xs 0 (len xs))) ; -3 -3 0 5 5 7 12
|
(show (slice xs 0 (len xs))) ; -3 -3 0 5 5 7 12
|
||||||
|
|
||||||
;; Reverse-sorted: the case a comparison that never fires would pass.
|
;; Reverse-sorted: the case a comparison that never fires would pass.
|
||||||
(set (at ys 0) 5) (set (at ys 1) 4) (set (at ys 2) 3)
|
(set (at ys 0) 5) (set (at ys 1) 4) (set (at ys 2) 3)
|
||||||
(set (at ys 3) 2) (set (at ys 4) 1)
|
(set (at ys 3) 2) (set (at ys 4) 1)
|
||||||
(sort-i32! (slice ys 0 (len ys)))
|
(sort! (slice ys 0 (len ys)))
|
||||||
(show (slice ys 0 (len ys))) ; 1 2 3 4 5
|
(show (slice ys 0 (len ys))) ; 1 2 3 4 5
|
||||||
|
|
||||||
;; A subslice, with the elements on both sides left alone.
|
;; A subslice, with the elements on both sides left alone.
|
||||||
(set (at zs 0) 100) (set (at zs 1) 9) (set (at zs 2) -1)
|
(set (at zs 0) 100) (set (at zs 1) 9) (set (at zs 2) -1)
|
||||||
(set (at zs 3) 9) (set (at zs 4) 4) (set (at zs 5) 0)
|
(set (at zs 3) 9) (set (at zs 4) 4) (set (at zs 5) 0)
|
||||||
(set (at zs 6) 200) (set (at zs 7) 300)
|
(set (at zs 6) 200) (set (at zs 7) 300)
|
||||||
(sort-i32! (slice zs 1 6))
|
(sort! (slice zs 1 6))
|
||||||
(show (slice zs 0 (len zs))) ; 100 -1 0 4 9 9 200 300
|
(show (slice zs 0 (len zs))) ; 100 -1 0 4 9 9 200 300
|
||||||
|
|
||||||
;; Degenerate lengths must do nothing rather than run off an end.
|
;; Degenerate lengths must do nothing rather than run off an end.
|
||||||
(sort-i32! (slice zs 0 0))
|
(sort! (slice zs 0 0))
|
||||||
(reverse-i32! (slice zs 0 0))
|
(reverse! (slice zs 0 0))
|
||||||
(sort-i32! (slice zs 2 3))
|
(sort! (slice zs 2 3))
|
||||||
(reverse-i32! (slice zs 2 3))
|
(reverse! (slice zs 2 3))
|
||||||
(show (slice zs 0 (len zs))) ; 100 -1 0 4 9 9 200 300
|
(show (slice zs 0 (len zs))) ; 100 -1 0 4 9 9 200 300
|
||||||
0)
|
0)
|
||||||
|
|||||||
@ -31,11 +31,11 @@
|
|||||||
(println "")
|
(println "")
|
||||||
|
|
||||||
;; First occurrence, and None for a byte that is not there.
|
;; First occurrence, and None for a byte that is not there.
|
||||||
(print (match (index-of-byte (bytes "banana") \a) (Some i) i None -1))
|
(print (match (index-of (bytes "banana") \a) (Some i) i None -1))
|
||||||
(print " ")
|
(print " ")
|
||||||
(print (match (index-of-byte (bytes "banana") \z) (Some i) i None -1))
|
(print (match (index-of (bytes "banana") \z) (Some i) i None -1))
|
||||||
(print " ")
|
(print " ")
|
||||||
(print (match (index-of-byte (bytes "") \a) (Some i) i None -1))
|
(print (match (index-of (bytes "") \a) (Some i) i None -1))
|
||||||
(println "")
|
(println "")
|
||||||
|
|
||||||
;; Accepted.
|
;; Accepted.
|
||||||
|
|||||||
@ -1320,6 +1320,13 @@ let () =
|
|||||||
outputs "a local shadows an imported name" "programs/pkg-shadow.flan"
|
outputs "a local shadows an imported name" "programs/pkg-shadow.flan"
|
||||||
"7\n20\n0\n5\n";
|
"7\n20\n0\n5\n";
|
||||||
|
|
||||||
|
(* Generics end to end: one written body per family, several emitted, and
|
||||||
|
the collapsed prelude running underneath it. Every line of the expected
|
||||||
|
output is an answer a per-type copy used to give. *)
|
||||||
|
let generics_out = "3\n4.5\ntrue\n7\n5\n-1\n5\n42\n3\n1\n10\n1\n8\n3\n4.5\ntext\n1\n2.5\n9\n36\n2\n2.5\n0\n3\n" in
|
||||||
|
outputs "generics" "programs/generics.flan" generics_out;
|
||||||
|
outputs ~opt:"-O0" "generics, -O0" "programs/generics.flan" generics_out;
|
||||||
|
|
||||||
(* Reach's walk, edge by edge. Pruning is what makes the link follow the
|
(* Reach's walk, edge by edge. Pruning is what makes the link follow the
|
||||||
program, and the cost of getting it wrong is not a wrong answer: a
|
program, and the cost of getting it wrong is not a wrong answer: a
|
||||||
function the walk fails to reach is not emitted, and the build dies in
|
function the walk fails to reach is not emitted, and the build dies in
|
||||||
@ -1368,6 +1375,24 @@ let () =
|
|||||||
in
|
in
|
||||||
(* Visibility: main is not a name a package offers, and saying so is the
|
(* Visibility: main is not a name a package offers, and saying so is the
|
||||||
point — "unknown name sand/main" would be true and useless. *)
|
point — "unknown name sand/main" would be true and useless. *)
|
||||||
|
(* Generics, at the definition rather than at a call site. Both of these
|
||||||
|
are refusals the abstract pass exists for: the body is checked once
|
||||||
|
with its type variables left abstract, so an operator the variable was
|
||||||
|
not declared to support, and an instantiation that grows without end,
|
||||||
|
are both answered where they are written. The second one used to *hang*
|
||||||
|
rather than fail, which through Session.eval is C-c C-c hanging with
|
||||||
|
the dev daemon behind it — so what is asserted is that it names the
|
||||||
|
chain of instantiations and not a depth it gave up at. *)
|
||||||
|
refuses "an unconstrained operator in a generic body"
|
||||||
|
"programs/generic-reject.flan"
|
||||||
|
"only what it is declared to support";
|
||||||
|
refuses "an unconstrained operator names the way out"
|
||||||
|
"programs/generic-reject.flan" "{:where (numeric? $t)}";
|
||||||
|
refuses "a runaway instantiation" "programs/generic-runaway.flan"
|
||||||
|
"instantiates itself without end";
|
||||||
|
refuses "a runaway instantiation names the chain"
|
||||||
|
"programs/generic-runaway.flan" "grow at ([2 i32])";
|
||||||
|
|
||||||
refuses "a package's main is not visible" "programs/pkg-hidden-main.flan"
|
refuses "a package's main is not visible" "programs/pkg-hidden-main.flan"
|
||||||
"sand/main is not a name";
|
"sand/main is not a name";
|
||||||
refuses "one directory under two aliases" "programs/pkg-two-aliases.flan"
|
refuses "one directory under two aliases" "programs/pkg-two-aliases.flan"
|
||||||
|
|||||||
@ -2118,6 +2118,86 @@ let () =
|
|||||||
| [] -> check "a report has a first line" false)
|
| [] -> check "a report has a first line" false)
|
||||||
| None -> check "a report needs a diagnostic" false);
|
| None -> check "a report needs a diagnostic" false);
|
||||||
|
|
||||||
|
(* ── Generics: the syntax, the predicates, and the two defaults ──
|
||||||
|
The syntax question the feature had to settle first: [{K V}] is a legal
|
||||||
|
*return type*, so a defn with a map return type and a constraint map puts
|
||||||
|
two braces in a row meaning different things. They are told apart
|
||||||
|
structurally, by the first form inside — a constraint map leads with a
|
||||||
|
keyword and a map type leads with a type — so [{K V}] did not have to go
|
||||||
|
and is still exactly what it was. *)
|
||||||
|
accepts "a map return type is still a map return type"
|
||||||
|
"(defn f [] {string i32} (map-new string i32))";
|
||||||
|
accepts "a map return type followed by a constraint map"
|
||||||
|
"(defn f [x $t] {string i32} {:where (copyable? $t)} \
|
||||||
|
(do x (map-new string i32)))";
|
||||||
|
rejects_check "a map return type is not read as a constraint map"
|
||||||
|
~needle:"is not a type variable of f"
|
||||||
|
"(defn f [] {string i32} {:where (ordered? $t)} (map-new string i32))";
|
||||||
|
|
||||||
|
(* The predicates, and each one gating the operator it is for. *)
|
||||||
|
accepts "ordered? admits <"
|
||||||
|
"(defn less [a $t b $t] bool {:where (ordered? $t)} (< a b))";
|
||||||
|
accepts "equal? admits ="
|
||||||
|
"(defn same [a $t b $t] bool {:where (equal? $t)} (= a b))";
|
||||||
|
accepts "numeric? admits +"
|
||||||
|
"(defn add [a $t b $t] $t {:where (numeric? $t)} (+ a b))";
|
||||||
|
rejects_check "equal? does not admit <"
|
||||||
|
~needle:"nothing here says t is ordered?"
|
||||||
|
"(defn less [a $t b $t] bool {:where (equal? $t)} (< a b))";
|
||||||
|
(* The entailments, which are the reason a signature is one predicate long
|
||||||
|
rather than three. Every type the language orders is a number or an enum,
|
||||||
|
so it is equatable and it is not move-only. *)
|
||||||
|
accepts "ordered? entails equal?"
|
||||||
|
"(defn same [a $t b $t] bool {:where (ordered? $t)} (= a b))";
|
||||||
|
accepts "numeric? entails ordered?"
|
||||||
|
"(defn less [a $t b $t] bool {:where (numeric? $t)} (< a b))";
|
||||||
|
accepts "ordered? entails copyable?"
|
||||||
|
"(defn twice [a $t] bool {:where (ordered? $t)} (< a a))";
|
||||||
|
rejects_check "a predicate nobody has heard of"
|
||||||
|
~needle:"is not a type predicate"
|
||||||
|
"(defn f [a $t] $t {:where (sortable? $t)} a)";
|
||||||
|
rejects_check "a predicate about a variable the signature never bound"
|
||||||
|
~needle:"is not a type variable of f"
|
||||||
|
"(defn f [a i32] i32 {:where (ordered? $t)} a)";
|
||||||
|
|
||||||
|
(* Move-only by default, which is the other half of the where clause and the
|
||||||
|
one with no Odin counterpart: Odin has no move semantics, so its $T never
|
||||||
|
has to answer. The prior art is Rust's T: Copy, and the difference is
|
||||||
|
that copyable? is a question the compiler answers rather than a trait a
|
||||||
|
user implements. Conservative in the safe direction — move is the
|
||||||
|
stricter rule, so assuming it can only refuse a valid program. *)
|
||||||
|
rejects_check "a type variable is move-only until it says otherwise"
|
||||||
|
~needle:"cannot be used again"
|
||||||
|
"(defn twice [a $t b (Fn [$t $t] $t)] $t (b a a))";
|
||||||
|
accepts "and copyable? is the opt-out"
|
||||||
|
"(defn twice [a $t b (Fn [$t $t] $t)] $t {:where (copyable? $t)} (b a a))";
|
||||||
|
|
||||||
|
(* The allow-list, and it has two members. println over a type variable is
|
||||||
|
deferred to the instantiation, because its legality is only decidable
|
||||||
|
after substituting — which is the one thing the abstract pass otherwise
|
||||||
|
refuses to do. *)
|
||||||
|
accepts "println over a type variable is deferred"
|
||||||
|
"(defn show [x $t] () {:where (copyable? $t)} (println x))";
|
||||||
|
accepts "and so is print"
|
||||||
|
"(defn show [x $t] () {:where (copyable? $t)} (print x))";
|
||||||
|
|
||||||
|
(* A predicate a body relies on has to be carried by every signature between
|
||||||
|
it and the call site, or the refusal moves into code the caller did not
|
||||||
|
write. *)
|
||||||
|
rejects_check "a predicate is not carried through a generic call"
|
||||||
|
~needle:"has to be carried by every signature"
|
||||||
|
"(defn outer [s [$t]] () {:where (copyable? $t)} (sort! s))";
|
||||||
|
accepts "and is accepted when it is"
|
||||||
|
"(defn outer [s [$t]] () {:where (ordered? $t)} (sort! s))";
|
||||||
|
|
||||||
|
(* A map key that is a type variable has no hash and no equality to emit:
|
||||||
|
they are chosen from the concrete type, which does not exist yet. *)
|
||||||
|
rejects_check "a map keyed by a type variable that is not hashable?"
|
||||||
|
~needle:"is not a map key"
|
||||||
|
"(defn f [m {$t i32}] i32 {:where (copyable? $t)} (len m))";
|
||||||
|
accepts "and hashable? is what says it is"
|
||||||
|
"(defn f [m {$t i32}] i32 {:where (hashable? $t)} (len m))";
|
||||||
|
|
||||||
(* ── The acceptance program checks end to end ──────────────────── *)
|
(* ── The acceptance program checks end to end ──────────────────── *)
|
||||||
accepts "calc-me.flan type checks"
|
accepts "calc-me.flan type checks"
|
||||||
(In_channel.with_open_bin "../calc-me.flan" In_channel.input_all);
|
(In_channel.with_open_bin "../calc-me.flan" In_channel.input_all);
|
||||||
|
|||||||
2
vendor/edn/edn.flan
vendored
2
vendor/edn/edn.flan
vendored
@ -320,7 +320,7 @@
|
|||||||
;; A ratio is caught here and not by a "contains a slash" rule over every
|
;; A ratio is caught here and not by a "contains a slash" rule over every
|
||||||
;; token, because a slash is perfectly ordinary in a symbol: foo/bar is a
|
;; token, because a slash is perfectly ordinary in a symbol: foo/bar is a
|
||||||
;; namespaced name and must stay one.
|
;; namespaced name and must stay one.
|
||||||
(when (match (index-of-byte text \/) (Some _) true None false)
|
(when (match (index-of text \/) (Some _) true None false)
|
||||||
(fail c err-ratio lo)
|
(fail c err-ratio lo)
|
||||||
(return (error-token c)))
|
(return (error-token c)))
|
||||||
(when (match (parse-i64 text) (Some _) true None false)
|
(when (match (parse-i64 text) (Some _) true None false)
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
;; `some` unwraps Some and early-returns None from *this* function.
|
;; `some` unwraps Some and early-returns None from *this* function.
|
||||||
(defn doubled-first [s [i32]] (Option i32)
|
(defn doubled-first [s [i32]] (Option i32)
|
||||||
(Some (* 2 (some (index-of-i32 s 15)))))
|
(Some (* 2 (some (index-of s 15)))))
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(match (doubled-first (slice nums 0 4))
|
(match (doubled-first (slice nums 0 4))
|
||||||
(Some i) (do (print i) (println "")) ; 4
|
(Some i) (do (print i) (println "")) ; 4
|
||||||
None (println "not found"))
|
None (println "not found"))
|
||||||
(match (index-of-i32 (slice nums 0 4) 99)
|
(match (index-of (slice nums 0 4) 99)
|
||||||
(Some i) (do (print i) (println ""))
|
(Some i) (do (print i) (println ""))
|
||||||
None (println "not found")))
|
None (println "not found")))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user