openlang

age>=18=>allow and the Racket reader

Rule authors want to write age>=18=>allow, no spaces and no parentheses. Tiny change, visually. Which layer gets to decide what counts as a token?

In Racket the reader builds the initial syntax representation. Read normally, that fragment can arrive as one symbol rather than the operators its author pictures. A macro could pick the symbol's spelling apart, but that's a little parser inside the notation, and it still can't redefine the lexical conventions around the macro. A custom reader fits when the language truly needs different token boundaries or delimiter rules.

Expansion does other work. It can reject malformed rule forms, set up bindings and translate a rule into calls. Runtime still has to get an age value, handle its absence and perform any permitted action. If an attempt form shows up later, expansion can arrange a call into a retry engine. Whether that engine repeats a test, an action or both is a runtime promise the notation must spell out.

A named language can package the reader and expander entry points when the choices hang together, at the price of documentation and tooling work. If the request means only calling an existing comparison and returning a decision, a plain library call may already say it clearly. The notation earns its place only if readers can still find their way.

The conversation continues 5

Follow the reasoning
  1. Permalink

    Run the attempt body twice. How many notifications went out? In Clojure, put a Ref update and a notification call inside the same dosync and a conflict can retry the body. The Ref change commits once. The notification can go out on every attempt. Punctuation can't fix that, so the form has to tell callers whether its body may run again. Returning notification data for later delivery keeps the attempts away from the effect. The state update is coordinated. Delivery still needs an answer.

  2. Permalink

    Who knows enough to choose a replacement when the threshold is missing? Quite possibly the caller, not the rule evaluator. So the evaluator can signal a condition at runtime and offer a replacement-value restart. A handler-bind handler can invoke that restart while the signaling context is still available. Returning normally instead declines to handle the condition. A handler-case clause is a different arrangement, since it runs after control has transferred and the stack has unwound. This is exactly where your split between expansion and runtime gets tested, and the notation should document which of these choices survive.

  3. Permalink

    age>=18=>allow reads nicely, I'll give it that. Now the author drops a letter and writes age>=18=>alow. What do they see? An error pointing at the action, or a shrug about one long mystery symbol? Ruby's NameError suggestions have spoiled me a bit. One missing letter ought to be easy to find.

  4. Permalink

    Yes, and the reader is the natural place to start. A custom reader can give the action token its own source location and hand that syntax to the expander. Then the work splits. The reader rejects broken token structure. The expander rejects an action name it doesn't know, attaches the diagnostic to alow, and offers allow purely as help. Nothing gets evaluated.

    Keeping the ordinary reader is possible, but then the macro that picks apart one symbol's spelling also has to track where the action starts inside it. Otherwise the narrower span is lost and the error lands on the whole symbol.

    Either way the suggestion never changes which program is accepted. That's the line I'd hold.

Other sides of the same question

  1. unsafe belongs on the extern block, not the call site

    Rust 2024 requires unsafe extern "C" { ... } instead of a bare extern block, and it's the most honest small syntax change the…

    Replies 4rustc