(max-of T) and (min-of T) are a numeric type's limits, at a concrete type or a type variable the bound admits, and given a slice they are still the prelude's reductions

This commit is contained in:
Joseph Ferano 2026-09-25 12:00:42 +07:00
parent 2ea47f91eb
commit 5c0698d749
5 changed files with 131 additions and 0 deletions

View File

@ -7371,6 +7371,60 @@ and named_call ?(qualified = false) ctx ~want loc name args =
expect ctx loc ~want expect ctx loc ~want
(List.fold_left (fun acc arg -> pick acc (check ctx ~want:ty arg)) (List.fold_left (fun acc arg -> pick acc (check ctx ~want:ty arg))
(pick a b) rest) (pick a b) rest)
(* (max-of T) and (min-of T): the type-limit constants, by type, so a
generic body can name its own type's. Odin's max(T) and min(T), and the
same answer for a float: the largest finite value and its negation, not
the smallest positive one. Given a value rather than a type, the name is
the prelude's reduction of a slice, and the call is an ordinary one —
the same split [vec-new] makes between a type and an allocator. *)
| ("max-of" | "min-of")
when (match args with
| [ a ] ->
type_of_expr a <> None
|| (match a.Ast.e with
| Ast.Var n ->
lookup ctx n = None
&& (not (Hashtbl.mem ctx.env.globals n))
&& type_named ctx n
| _ -> false)
| _ -> false) ->
let a = List.hd args in
let ty =
match type_of_expr a, a.Ast.e with
| Some t, _ -> resolve ctx.env t
| _, Ast.Var n -> resolve_name ctx.env ~seen:[] a.Ast.loc n
| _ -> fail a.Ast.loc "internal: max-of's type argument is not a type"
in
let max = String.equal name "max-of" in
let v =
match ty with
| Types.Int k ->
let b = Types.bits k in
let n =
if Types.signed k then
let top = Int64.shift_left 1L (b - 1) in
if max then Int64.sub top 1L else Int64.neg top
else if not max then 0L
else if b = 64 then -1L
else Int64.sub (Int64.shift_left 1L b) 1L
in
mk loc ty (Tast.Int (n, k))
| Types.Float k ->
let m =
match k with
| Types.F32 -> Int32.float_of_bits 0x7f7fffffl
| Types.F64 -> Float.max_float
in
mk loc ty (Tast.Float ((if max then m else -.m), k))
| Types.Var _ ->
unconstrained ctx.env loc name ~needs:"numeric?" ty;
int_literal loc ~want:(Some ty) ~preds:ctx.env.tvpreds 0L
| _ ->
fail a.Ast.loc
"%s takes a numeric? type, and %s is not one — as in (%s i32)" name
(Types.to_string ty) name
in
expect ctx loc ~want v
(* (zeroed) is the all-bytes-zero value of whatever it is being stored into, (* (zeroed) is the all-bytes-zero value of whatever it is being stored into,
so it only means anything where a type is expected of it. *) so it only means anything where a type is expected of it. *)
| "zeroed" -> | "zeroed" ->

View File

@ -406,6 +406,8 @@ let source = {flan|
;; or more numbers, and a defn cannot shadow a builtin: nothing shadows [+] ;; or more numbers, and a defn cannot shadow a builtin: nothing shadows [+]
;; either. These reduce a slice, which is a different operation with a ;; either. These reduce a slice, which is a different operation with a
;; different arity, so the different name is honest rather than a workaround. ;; different arity, so the different name is honest rather than a workaround.
;; Given a type instead of a slice, (min-of i8) and (max-of $t) are the type's
;; limits, and the checker answers those itself.
(defn min-of [s [$t]] (Option $t) (defn min-of [s [$t]] (Option $t)
{:where (ordered? $t)} {:where (ordered? $t)}
(if (= (length s) 0) (if (= (length s) 0)

54
test/programs/max-of.flan Normal file
View File

@ -0,0 +1,54 @@
;;;; (max-of T) and (min-of T): a numeric type's limits, named by the type, at
;;;; a concrete type and inside a generic whose bound admits numbers.
;; A selection sort, descending, whose running best starts at the least value
;; of the element type, so any element beats it.
(defn sort-desc [s [$t]] ()
{:where (numeric? $t)}
(dotimes [i (length s)]
(let [best (min-of $t)
at-best i]
(dotimes [j (- (length s) i)]
(let [k (+ i j)]
(when (> (at s k) best)
(set best (at s k))
(set at-best k))))
(swap s i at-best))))
(defn largest [s [$t]] $t
{:where (numeric? $t)}
(let [best (min-of t)]
(dotimes [i (length s)]
(when (> (at s i) best) (set best (at s i))))
best))
(defn show-i32 [s [i32]] ()
(dotimes [i (length s)] (print (at s i)) (print " "))
(println ""))
(defn show-f64 [s [f64]] ()
(dotimes [i (length s)] (print (at s i)) (print " "))
(println ""))
(defn main [] i32
(println (max-of u8))
(println (min-of u8))
(println (max-of i8))
(println (min-of i8))
(println (max-of i32))
(println (min-of i64))
(println (max-of u64))
(println (= (max-of f32) f32-max))
(println (= (min-of f64) (- f64-max)))
(println (= (max-of i16) i16-max))
(let [a [(i32 3) -7 12 0 -2147483648 5]
b [2.5 -1.0 1e300 -1e308]
c [(u8 4) 0 200 9]]
(sort-desc (slice a))
(show-i32 (slice a))
(sort-desc (slice b))
(show-f64 (slice b))
(println (largest (slice c)))
(println (let [d [(i64 -5) -9]] (largest (slice d))))
(println (let [e [(f32 -1.0) -3.0]] (= (largest (slice e)) (f32 -1.0)))))
0)

View File

@ -573,6 +573,14 @@ let () =
outputs "a prelude function shadowed" "programs/shadow-prelude.flan" sp_out; outputs "a prelude function shadowed" "programs/shadow-prelude.flan" sp_out;
outputs ~x86:true "a prelude function shadowed, x86" outputs ~x86:true "a prelude function shadowed, x86"
"programs/shadow-prelude.flan" sp_out; "programs/shadow-prelude.flan" sp_out;
(* (max-of T) and (min-of T), concrete and inside a generic. *)
let maxof_out =
"255\n0\n127\n-128\n2147483647\n-9223372036854775808\n\
18446744073709551615\ntrue\ntrue\ntrue\n\
12 5 3 0 -7 -2147483648 \n1e+300 2.5 -1 -1e+308 \n200\n-5\ntrue\n" in
outputs "max-of and min-of" "programs/max-of.flan" maxof_out;
outputs ~opt:"-O0" "max-of and min-of, -O0" "programs/max-of.flan" maxof_out;
outputs ~x86:true "max-of and min-of, x86" "programs/max-of.flan" maxof_out;
(* (- x) negates, on every numeric type, a type variable and a dyn. *) (* (- x) negates, on every numeric type, a type variable and a dyn. *)
let neg_out = let neg_out =
"-3\n7\n-2.5\n-inf\n-1.5\n255\n-4\n-2.5\n-inf\n-9000000000\n\ "-3\n7\n-2.5\n-inf\n-1.5\n255\n-4\n-2.5\n-inf\n-9000000000\n\

View File

@ -6399,6 +6399,19 @@ let () =
"(defn main [] i32 (let [a [None None]] 0))" "(defn main [] i32 (let [a [None None]] 0))"
~needle:"what None is an Option of"; ~needle:"what None is an Option of";
(* ── (max-of T) and (min-of T) ──────────────────────────────────── *)
infers "max-of carries its type" "(max-of u16)" "u16";
infers "min-of at a float" "(min-of f32)" "f32";
rejects_check "max-of at an unbounded type variable names the bound"
"(defn f [x $t] $t (max-of $t))" ~needle:"{:where (numeric? $t)}";
accepts "max-of at a type variable the bound admits"
"(defn f [x $t] $t {:where (integer? $t)} (max-of t))";
rejects_check "max-of at a type that is not a number names the bound"
"(defn f [] string (max-of string))"
~needle:"max-of takes a numeric? type, and string is not one";
accepts "max-of of a slice is still the prelude's reduction"
"(defn f [xs [i32]] (Option i32) (max-of xs))";
(* ── (the T e) ─────────────────────────────────────────────────── *) (* ── (the T e) ─────────────────────────────────────────────────── *)
infers "the gives a literal its type" "(the u8 200)" "u8"; infers "the gives a literal its type" "(the u8 200)" "u8";
infers "the widens as an annotation does" "(the i64 (the i32 1))" "i64"; infers "the widens as an annotation does" "(the i64 (the i32 1))" "i64";