Should "Exotic Fields" Be Able To Examine PACK! ?

The general pitch of unstable block antiforms is that they decay to their first element in assignments to single words:

>> x: pack [1 2]
== ~['1 '2]~  ; anti

>> x
== 1

SET-BLOCK! is one of the things that is able to do special handling to pick apart the pack:

>> [x y]: pack [1 2]
== ~['1 '2]~  ; anti

>> x
== 1

>> y
== 2

But you can create your own operators that work with packs... they're just antiform blocks, after all. If you take an argument as ^META and typecheck for packs you can do whatever you like. You could re-implement the functionality of SET-BLOCK! yourself if you felt like it, designing whatever you want:

>> my-10times-assigner [x y] pack [1 2]
== ~['1 '2]~

>> x
== 10

>> y
== 20

But Should Plain Assignments Ever Process Packs?

e.g. should this hypothetical PACK-SUMMER! be possible, which assigns the sum of pack elements?

 >> type of ps
 == ~{pack-summer!}~

 >> ps.x: pack [10 20]
 == ~['10 '20]~

 >> ps.x
 == 30  ; e.g. the assignment received 10 + 20

You can imagine other ideas, such as reacting to raised errors somehow, vs. having an enforced pattern of skipping the assignment and propagating the raise. :face_with_diagonal_mouth:

Freedom To vs. Freedom From

This doesn't seem like a wise axis of extensibility to me.

The possible benefits appear outweighed by the loss of certainty about how things will act.

It also means doing minor transformations would wreck things.

 >> temp: pack [10 20]
 == 10

 >> ps.x: temp
 == 10

But while that may seem damning, it's the case for any time a function takes an unstable antiform... you can't factor the expression and expect it to work the same. To truly preserve things, you have to do lift and unlift.

>> temp: lift pack [10 20]
== ~['10 '20]~

>> ps.x: unlift temp
== ~['10 '20]~  ; anti

>> ps.x
== 30

But still... I just don't think I like the idea of having what looks like an assignment be able to subvert the expectations that you're taking decayed information.

The one exception I am making is for the empty ~[]~ antiform, which indicates a desire to remove the key itself. So the assignment (POKE) protocol gets 0 or 1 values. I think that one exception is enough.

I'm finding that many questions seem to be answered by LIFT the UNIVERSE. The answers additionally seem to be mostly good.

Here it wouldn't be possible, unless you use ^META assignments, in which it is the explicit job of those sinks to decide if they're willing to store the unstable antiform or not. (e.g. BLOCK!s don't, OBJECT!s do, etc.)

Your hypothetical pack-summer could exist, it would just have to say ps.^x: pack [10 20] to receive the unstable form.

SHOULD it exist? :thinking: I don't think so.

The low-level assignment operator presumes you stored the bits as given. But this could change for any reason.. e.g. if you assign env.PATH: %some-path/ the synthesized value of that expression witll always match the right hand side. But other processes could run and env.PATH could give another value entirely on the next fetch.

Your pack-summer would similarly not be able to make the result of ps.^x: pack [10 20] return anything other than the pack [10 20], because that's the contract of the assignment operator and beyond the control of the target. But the next time you ask for ps.x I guess it can say whatever it wants... including but not limited to the sum of the members of the last assignment's pack.

Until I can think of a good counterexample, I don't think that assignments should be explicitly looking for ways to "get creative" at doing things that don't model value-preserving assignment.

Yet if you use a ^META assignment, then essential to the design is that it can see unstable isotopes now. I can't think of a good way to stop anyone from implementing features that don't model assignments... besides asking nicely not to.

But I don't need psychic powers to tell you that ACCESSOR, GETTER, SETTER are going to be used to implement people's arbitrary wacky ideas. Maybe I should ask the AIs if there are good examples.

Your thinking is right on the money—once you allow generalized or overridable accessors (e.g. customizable get and set hooks), you're opening a pretty powerful and potentially unpredictable axis. Rebol’s goal of flexibility puts it in the same category as other dynamic and extensible languages like Python, Ruby, Lua, and Smalltalk, all of which have wrestled with similar questions.

:puzzle_piece: Key Principles and Guidance in Other Languages

1. Python (__getattr__, __setattr__, property)

Python’s property mechanism, along with __getattr__ and __setattr__, allow fine-grained interception of attribute access. The design philosophy is generally:

  • Guidance: Properties should behave as much like normal attributes as possible. Python’s Zen (PEP 20) includes "Special cases aren't special enough to break the rules" and "In the face of ambiguity, refuse the temptation to guess."
  • "Respectable" abuses: Some libraries override __setattr__ to implement reactive programming (e.g. Traits, or GUI toolkits like Kivy). Others use __getattr__ to implement delegation (e.g., forwarding to another object).
  • Gotchas: "Magical" behavior is frowned upon in core Python culture. Overuse of properties or dynamic dispatch can confuse debugging or introspection.

2. JavaScript (ES6 Proxies)

JavaScript’s Proxy is a more radical extensibility mechanism.

  • Guidance: The documentation is surprisingly permissive. MDN mostly warns: "Use with care", since proxies can break invariants.
  • "Respectable" abuses: Frameworks like Vue.js use proxies for reactive data binding. Also used in mocking frameworks and remote object access (e.g. implementing RPC).
  • Notable abuses: Overriding every trap (e.g. get, set, has, ownKeys) to produce surreal behavior that breaks expectations—like making an array that has no length but still returns elements.

3. Ruby (method_missing, define_method, attr_*)

Ruby’s metaprogramming is quite powerful and commonly used.

  • Guidance: Emphasizes "principle of least surprise". Rubyists often tolerate a bit more magic, but it should remain intuitive for humans.
  • "Respectable" abuses: method_missing is used by Rails’s ActiveRecord to implement dynamic finders like find_by_name_and_age. It’s accepted despite being a kind of "lies to the interpreter" trick.
  • Risks: Performance costs, fragile stack traces, and harder tooling (e.g., autocompletion, static analysis).

4. Lua (__index, __newindex metatables)

Lua’s metatables let you intercept almost anything.

  • Guidance: Lua's documentation often avoids strong norms and lets you explore, but "don't be surprised if you shoot yourself in the foot."
  • "Respectable" abuses: Transparent proxying, lazy-loading fields, implementing classes/inheritance.
  • Common pitfalls: Debuggability, accidentally triggering infinite recursion.

:compass: Guidance You Might Offer in Your Language

Given your control of the language and philosophy, you might consider offering the following principles:

  1. Predictability Rule
    “Accessor overrides should behave as if you were reading or writing a normal variable, unless you clearly document why not.”
  2. Value-Preservation Is a Goal, Not a Guarantee
    “Assignments must evaluate to the value being assigned, but you are not required to preserve it in the backing store. However, if your abstraction behaves like a variable, consider round-trip fidelity important.”
  3. The Principle of Least Magic
    “Avoid designs where the result of a fetch bears no discernible relation to what was assigned, unless modeling something where that surprise is intrinsic (e.g. time-varying state, remote resources).”
  4. Use ‘Lie Detectors’ When Lying
    “If you make a value look like a field but it’s a computation or a transformation, consider exposing metadata or debug tools to make that visible.”
  5. Safe Mutation by Convention
    Encourage idioms where destructive changes (e.g. setting env.PATH) return a confirmation or log metadata—e.g. env.PATH: %/somewhere/ returns %/somewhere/ (unchanged) or a mutation result token.
  6. Immutability/Historicity by Option
    Support patterns where a SET doesn't truly overwrite, but maybe pushes a version to a stack or journal, so behavior is rational and inspectable.

:brain: “Respectable Abuses” You Might Embrace

  • Aggregation/Reduction Setters
    Assigning [10 20] and then reading back 30 could be fine if the name implies it (summer.x)—think of it like a mini DSL. Lua folks often do this for numeric reductions.
  • Reactive Triggers
    Allow assignments to trigger actions, not just mutate state. Like spreadsheets: setting cell.A1 might cause cell.B1 to update.
  • Virtual Fields
    foo.temperature_c could be an alias for foo.temperature_f, with a getter/setter converting the units. Many languages do this.
  • Remote/Deferred Fields
    Assignments might schedule or buffer a change (e.g., sending a remote signal), not commit it immediately.

You’re in a good position to create a healthy culture by framing this extensibility not as "go nuts", but as "opt-in powers with cultural norms." Offer escape hatches, but teach people to look for handles.

Would you like a summary with examples of best practices, common traps, or clever/legit abuses to include in your language docs?