How Console Displays Things With No Literal Representation

It's tricky to think of what to do to print out things that don't have representation.

For example, antiforms don't have representations and cannot be molded. (For a proposal of the exception of MOLD of splices, see MOLD:ONLY vs mold spread)

So what's traditionally been done is just render it as a quasiform (so the tilde at the beginning hints something its up), and then add a comment at the end of the rendering.

>> anti 'null
== ~null~  ; anti

The representational issue is a bit of a thorn in the console, but you have to print something. (Well, unless it's TRASH!, where printing nothing is the design.)

Could it perhaps work to just print anti 'null? (Or unlift '~null~, as the case may be.) After all, they don’t really have a syntactic representation to print out in the first place.

For prior art, Haskell often does this kind of thing:

ghci> import Data.Map.Strict
ghci> fromList [('a',1), ('b',2), ('c',3)]
fromList [('a',1),('b',2),('c',3)]
ghci> singleton 'a' 1
fromList [('a',1)]
ghci> empty
fromList []

Among things that have historically lacked literal forms are objects, so giving back code is what it has done:

>> make object! [a: 10 b: 20]
== make object! [a: 10 b: 20]

The idea that all things need literal forms gave rise to "construction syntax", which was a series of proposals going in this direction:

>> make object! [a: 10 b: 20]
== #[object! [a: 10 b: 20]]

Avoiding that direction and having the console endorsing giving back things that evaluate to the result is another idea:

>> first [a b c]
== 'a

>> any [1 > 2, 3 > 4]
== ~null~

But would be confusing if you didn't do it systemically, e.g. left the quote off of inert things:

>> first [[a b] c d]
== [a b]

>> first ['[a b] c d]
== ''[a b]

So I think the quote would probably need to always be there... and maybe people would get used to it to the point it would be acceptable. Generic quoting opens the door to it being an option.

>> print "Void outputs should still be skipped, probably?"
Void outputs should still be skipped, probably?
== '

>>

This could work if done systematically… but then, you have to consider that you’re no longer looking at the result of your evaluation, but something which evaluates to the result. That feels confusing.

I think there’s a reasonable compromise similar to what is done now: show the literal form of the value if it has one, otherwise give back code which evaluates to that value. That would be unambiguous and consistent, but still easy to read.

2 posts were split to a new topic: Why is TYPE OF 1 an &INTEGER and not INTEGER!

LIFT the UNIVERSE is making an already-fraught issue a bit more fraught (but I'm pretty sure it's the right answer, regardless...)

This has a kind of weird implication for the "molding" of objects where you've set a field to an unstable antiform:

>> obj: make object! [try err: fail "for instance"]
== &[object! [
    ^err: ~&[warning! [...for instance...]]~
]]

To "be accurate", the molding has to show a ^err: vs. err: if what's encoded in the field is an unstable antiform. But if there's not a way to say "suppress" then what it's showing doesn't line up with executable code.

(It doesn't anyway...)

...BUT this is just another edge where it gets weirder.

I have discussed leading colon acting as TRY.

>> obj: make object! [try err: fail "for instance"]
== &[object! [
    :^err: ~&[warning! [...for instance...]]~
]]

Anyway, not the biggest deal in the world, just an observation.

There are more implications than this...

An arbitrary GETTER could have side effects... such as printing out "Field accessed". That means the molding or debug display either has to avoid calling the getter and say "can't display" or suffer the potential side-effect that reading the value will produce some output.

>> obj: make object! [fake: getter [print "HI!" 10 + 20]]
HI!
== &[object! [
    fake: 30  ; or avoid the call, e.g. "fake: ...calculated..."
]]

In an interactive UI, you could more easily offer a choice, that if someone wanted to perform the calculation they could click a button or somesuch.

These problems aren't new... and asking AI it says "yeah, that happens".

This seems like a good argument for having a "cache state" of the variable in the getter/setter. e.g. an accessor works in tandem with a value slot that it may ignore (or not), and what debug views show is this value... not the result of the calculation.

; Instead of this:
getter: func [] [compute something dynamic]
setter: func [value] [mutate system using value]

; I propose:
getter: func [last-value] [maybe return it, or recompute]
setter: func [last-value new-value] [decide what to do]

This gives your meta-object protocol the ability to:

  • Support non-volatile fields by returning last-value directly.
  • Be debug-safe by default, as tools can show last-value without invoking arbitrary computation.
  • Let getters/derivers cache results or revalidate staleness.
  • Track diffs or even maintain version histories.

Benefits

  • Safer tooling: You can decouple what’s shown from what’s executed.
  • Deterministic “reveal” UX: Debug tools could use a "refresh" or "evaluate" button instead of blindly probing.
  • Undo/redo history: setter(old, new) naturally supports undo systems or versioning.
  • Watchpoints and diagnostics: It’s easier to add logs, triggers, or “dry run” simulations.

:scroll: Precedent in Other Systems

This is quite similar to:

  • Model-View-Controller patterns in UI toolkits, where "Model" holds the value, and "Controller" lets you mutate/react.
  • Elm-style or Redux state, where you have a "last known state" and dispatchers that compute new ones from it.
  • React hooks (e.g. useState), where setters are called with old/new pairs.
  • Spreadsheet recalculation models, where the cell has a cached value and only recalculates on demand.

More implications still... e.g. what to show for the "true unset" state.

Now that: (^x: ~) and (x: ~) mean the same thing, and you have to use (tweak $x ~) to go to the "truly, truly unset level"... it works, but since it doesn't use assignment, how to display it?

 >> f: make frame! negate/
 == &[frame! [
     ?number?: ...???...  ; initialized to "true unset"
 ]

 >> tweak f.number null  ; passing NULL just peeks the value (should it be PEEK?)
 == \~\  ; antiform

 >> f.number: ~  ; normal trash/tripwire
 == \~\  ; antiform

 >> f
 == &[frame! [
     number: ~
 ]

 >> tweak f.number null  ; passing NULL just peeks the value, again
 == ~  ; tweak speaks in lifted band for "normal values"

I don't want to make it overblown that displaying the states of objects may not correspond to patterns as we might have known them...

...what's important is that the representation is coherent and queryable and that the system works. These are display issues which have a long history. How to show cyclic structures, etc. etc. is not going to sort out now... or really ever. We just need a casual answer that "works" for day-to-day.

Maybe showing antiforms is the answer...

 >> f: make frame! negate/
 == &[frame! [
     number: \~\  ; "unset"
 ]]

There's no such thing as that. But that's what it would be, if there was such a thing as that. :frowning:

e.g. if you could put an antiform into a block (you can't) and if you could evaluate antiforms to produce another state (you can't), we might imagine them producing anti-antiforms.

Bordering on nonsense, but enough to have something to display for now. All I care is that the design works.