;;;; Converting between an enum and an integer, both ways, explicitly. ;;;; ;;;; An enum is an i32 at run time and its own type in the checker. That is ;;;; what makes :spcae an error at the call site rather than a wrong number ;;;; later, and it is deliberately not weakened here: a bare integer still ;;;; does not fit an enum parameter, and a keyword still resolves against the ;;;; enum the site expects. What is added is a way to *say* the conversion, ;;;; by name, where it is meant — (i32 k) and (K n). ;;;; ;;;; Both directions are free. emit.ml's cast reduces an enum to its i32 ;;;; before choosing an instruction, so src and target are the same type and ;;;; the value is answered unchanged; there is no instruction to see at -O0 ;;;; either, which is why this runs at both optimisation levels. (defenum K [lo -1 mid 0 hi 1]) (defn num [k K] i32 (i32 k)) ;; An enum parameter driven by a loop variable, which is the shape this exists ;; for: the caller has an index, not a member. (defn name-at [i i32] string (let [k (K i)] (cond (= k :lo) "lo" (= k :mid) "mid" (= k :hi) "hi" :else "other"))) (defn main [] i32 ;; enum → i32. Lossless by construction: i32 is the representation. (println (num :lo)) (println (num :mid)) (println (num :hi)) ;; i32 → enum, then back. A round trip is the identity both ways because ;; neither direction is a conversion at run time. (println (num (K 1))) (println (i32 (K (num :lo)))) ;; A value that is no declared member is allowed. raylib's gesture bitfield ;; is an OR of flags and is exactly this, and the printer already falls ;; through to the number for an out-of-range enum, so refusing to construct ;; one while agreeing to print it would be incoherent. (let [odd (K 7)] (println (num odd)) ;; And printed as itself, which is the half that makes this coherent: the ;; structural printer is a comparison chain over the declared members and ;; falls through to the number when none match. Agreeing to show a value ;; outside the members while refusing to build one would be the ;; incoherence. (println odd) ;; The contrast: a value that IS a member prints as the member. (println (K 1)) (println (if (= odd :hi) "member" "not a member")) ;; The comparisons the raw bitfield wants, which need nothing beyond the ;; conversion: the enum goes to i32 and the literal follows it. (println (if (> (i32 odd) 3) "above 3" "not above 3"))) ;; A narrower or wider target truncates and extends by the same rule every ;; int→int cast follows — lo is -1, so i64 sign-extends and u8 wraps. (println (i64 (K -1))) (println (u8 (K -1))) ;; A float target means (f32 (i32 k)); nothing special. Written as (K 1) ;; rather than :hi because a bare keyword outside an enum-typed position has ;; no enum to resolve against, and that refusal is unchanged. (println (f32 (K 1))) ;; And the index-driven loop the whole thing is for. (dotimes [i 4] (println (name-at (- i 1)))) 0)