From f35af50f517a1b7749b171567fcd63eacd69b121 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 19 Sep 2026 04:30:57 +0700 Subject: [PATCH] or-else is Java's, not Rust's, and the None it is tested against is reached MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- lib/prelude.ml | 12 ++++++------ test/programs/edn-read.flan | 5 ++++- test/programs/generics.flan | 20 +++++++++----------- vendor/edn/read.flan | 7 ++++--- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/lib/prelude.ml b/lib/prelude.ml index ff91256..ea4c42b 100644 --- a/lib/prelude.ml +++ b/lib/prelude.ml @@ -385,12 +385,12 @@ let source = {flan| ;; bootstrap hook at the foot of this file), and a macro has ;; no way to report a malformed call. -;; The name is Rust's `or_else`; the behaviour is Rust's `unwrap_or`, and the -;; split is deliberate. Flan has no laziness, so both arguments are already -;; evaluated when this is entered — `or_else` there takes a closure and this -;; does not — and `or-else` is the spelling that reads at the call site, where -;; the default is nearly always a literal or a constructor with nothing to -;; defer. +;; This is Java's `Optional.orElse`, hyphenated: eager, and it answers the +;; payload's type rather than another Option. Worth saying which, because Rust +;; spells something else `or_else` — there it takes a closure and answers an +;; `Option`, so borrowing that name for this behaviour would be the wrong +;; loan twice over. Java's own lazy sibling is `orElseGet`, which is the one +;; declined above. ;; ;; **At a $t that owns storage the result is a header copy, and the branch not ;; taken is still the caller's to free.** Since the copyable? repeal every diff --git a/test/programs/edn-read.flan b/test/programs/edn-read.flan index 9f0b471..358fd0e 100644 --- a/test/programs/edn-read.flan +++ b/test/programs/edn-read.flan @@ -112,7 +112,10 @@ ;; and it is written as a defn here because the *initialiser* is still refused ;; — "a global's value must be a compile-time constant — this one is computed" ;; — which is a separate piece of work on globals and nothing to do with -;; read-file or or-else. Everything inside the with-allocator is verbatim. +;; read-file or or-else. Everything inside the with-allocator is verbatim, so +;; the day a computed initialiser is allowed, load-game-data and the `set` +;; below it collapse back into the defvar above and this comment goes with +;; them. ;; ;; The texture path printed here has to be the one show-tileset printed, which ;; is what says read-file read the file and not merely something. diff --git a/test/programs/generics.flan b/test/programs/generics.flan index 669455d..7fb2f1c 100644 --- a/test/programs/generics.flan +++ b/test/programs/generics.flan @@ -76,16 +76,14 @@ 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 rather than a bare None at the -;; call site because a bare None there would have to take its element type from -;; or-else's own signature, and the point is to arrive with the Option already -;; built — the same way edn/read arrives with one. -(defn opt-vec [full? bool] (Option (Vec u8)) - (if full? - (let [v (vec-new u8)] - (push v 90) - (Some v)) - None)) +;; 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. @@ -174,7 +172,7 @@ (push full 65) (push full 66) (println (len (or-else (Some full) empty))) ; 2, full's header - (println (len (or-else (opt-vec false) empty))) ; 0, empty's + (println (len (or-else (none-vec) empty))) ; 0, empty's (free full) (free empty)) diff --git a/vendor/edn/read.flan b/vendor/edn/read.flan index afbff64..85e5d68 100644 --- a/vendor/edn/read.flan +++ b/vendor/edn/read.flan @@ -276,9 +276,10 @@ ;; Option exists for. And this function has nothing to answer a FileError ;; *with* — `use-value` wants a path only the caller knows, and whether a ;; missing file is fatal or is a cue to write a default is the caller's policy -;; in every program. So slurp's condition and both its restarts arrive at the -;; caller's handler-bind unchanged, including a `use-value` that names another -;; path: the read then happens against that file and this function never knows. +;; in every program. Nothing here establishes a handler, so slurp's condition +;; reaches the caller's with both restarts still armed; edn-read.flan runs the +;; `use-value` half, where the handler names another path, the read happens +;; against that file, and this function never learns that anything went wrong. (defn read-file [path string] (Option Value) (let [src (slurp path (heap-allocator))] (defer (free src))