Paths, .field, hoisting, and (std hashset)

- (.field value), and get, try-get, set and update for reading and copying
  along a path of fields and keys, with ref-set in the generic operations.
- please-hoist-i-promise-i-am-not-naughty, in the macros section.
- (std set) is (std hashset), and the collector is hash-setting.

🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-5)

Co-Authored-By: eca-agent <git@eca.dev>
This commit is contained in:
Linus Björnstam 2026-10-03 15:03:56 +02:00
parent 1fe22a4dfd
commit 48771a225a
3 changed files with 85 additions and 11 deletions

View file

@ -97,7 +97,7 @@ A list-shaped module path such as @code{(std random)} refers to a standard libra
| Module | What it has |
|---------------------------------+------------------------------------------------------|
| @code{(std random)} | random numbers, @code{shuffle-vec} |
| @code{(std set)} | sets; @code{(std orderedset)}, @code{(std orderedmap)} |
| @code{(std hashset)} | sets; @code{(std orderedset)}, @code{(std orderedmap)} |
| @code{(std ports)} | reading a whole port: @code{port->lines} and friends |
| @code{(std fmt)} | text layout |
| @code{(std rx)} | regular expressions |
@ -178,7 +178,7 @@ Imports can be wrapped in modifiers to filter which definitions are brought into
0)
}|
Modifiers can be nested and evaluate from the inside out. For example, @code{(prefix (except (std set) set-map) "s/")} excludes @code{set-map} and prefixes all remaining names with @code{s/}.
Modifiers can be nested and evaluate from the inside out. For example, @code{(prefix (except (std hashset) hashset-map) "s/")} excludes @code{hashset-map} and prefixes all remaining names with @code{s/}.
Note that @code{only} and @code{except} apply exclusively to functions and macros, not types. A module's exported types are always imported, ensuring that signatures referencing those types remain valid and resolvable:
@ -1177,6 +1177,33 @@ carries a list, so this literal cannot be one. Its tags are play, say or pass.
This elaboration mechanism only applies in positions where the type checker already knows that a specific union type is expected (which is why @code{script} and @code{more} include explicit type signatures). Standard library modules like @code{(std run)} and @code{(std fmt)} use this exact technique for command pipelines and text layouts.
@subsubsection{Computing a value once}
A macro often builds a value out of constants, and that value is then built again every time the expansion is evaluated. When that is expensive, wrap it in @code{please-hoist-i-promise-i-am-not-naughty}: the form is evaluated the first time it is reached, and every later evaluation gets that same value. This is what @code{#rx(...)} does, so a regex is compiled once rather than once per match.
@codeblock[#:lang "bjolang"]|{
;; (tag-set "a" "b" ...) is a HashSet of the strings, built once.
(def/macro (tag-set form inject compare)
(syntax-match form
((_ tag ...)
#'(please-hoist-i-promise-i-am-not-naughty
(list->hashset (list ,@tag))))))
}|
If computing the value throws, nothing is kept, and the next evaluation tries again. The same form written in two places is one value.
The long name is a promise about what the compiler cannot check: that the form has no side effects worth repeating, and that it does not depend on things that change from call to call, such as a parameter or an effect handler. Everything it can check, it does, and a form that breaks a rule is a compile error that lists every reason. The form cannot:
@itemlist{
@item{refer to a local variable or a function parameter;}
@item{read a @code{def} of the module it is in, or any @code{def/mutable};}
@item{use a type variable of the function around it;}
@item{call a bjoroutine, @code{yield} to a sequence, or leave a @code{with-return};}
@item{be void, or produce something that can be changed in place, such as an array, a box or a mutable collection.}
}
It may use literals, functions, and the defs of other modules. A macro that puts its caller's code inside the form passes these rules on to its caller, so keep the form to parts the macro built itself.
@subsubsection{Macro limitations}
When designing macros, keep the following constraints in mind: