The Rules Of Duals

With the arrival of ALIAS, it's one of the first actually deployed "bedrock states"... with all of the questions and issues that they bring up:

https://rebol.metaeducation.com/t/the-return-of-alias/2129

You Can't Detect A Bedrock State By GET-ing A Variable

So for example: once you set up a variable as an alias, if you're just using normal functions, there's no way you can tell it's an alias.

Let's write an unstable antiform pack into an alias:

>> x: 10

>> y: alias $x

>> ^y: pack [1 2]
== \~['1 '2]~\  ; antiform (pack!)

>> ^x
== \~['1 '2]~\  ; antiform (pack!)

You used ^META access...the "lowest-level" way to evaluate, and you saw an unstable antiform.

But that's the end of the line. Reading can't "see" the bedrock state. You have to use some kind of special access function, something like get-alias $y, to find out.

Assignments with SET-ing Are A Different Story...

At first I thought you wouldn't be able to write a bedrock state to a variable through normal evaluation, either.

But I realized there was a loophole.

The loophole comes from noticing that historically, PACK! only uses lifted states for its contents:

>> pack [1020, null, quote 304]
== \~('1020 ~null~ ''304)~\  ; antiform (pack!)
  • If you started with an antiform, you got a quasiform
  • If you started with a plain value, you got a quoted
  • If you started with a quoted value, it got another quote level

But you'd never see PACK! with--say--a plain WORD!, or RUNE!, or BLOCK!, or FRAME!, or INTEGER!...

Those packs would be considered "undecayable", and were errors.

There are exactly as many of these states as the number of states that bedrock cells can have.

So if instead of saying that decaying them is necessarily an error, we could say that if you assign such a PACK! to a plain variable, that means "assign as bedrock". I've done that with alias, where it uses META-WORD and META-TUPLE as the aliasing forms.

I've been calling such packs "undecayed bedrock"...

>> alias $x
== \~(^x)~\  ; antiform (pack!) "bedrock: alias"

So that is an undecayed alias.

You'd get such values back from an UNDECAY function:

>> x 10

>> y: alias $x

>> get $y
== 10

>> undecay $y
== \~(^x)~\  ; antiform (pack!) "bedrock: alias"

Obviously it can't bring back values that are no longer there:

>> z: pack [10 20]

>> undecay $z
== 10

So it only really "undecays" bedrock.

This Is Spooky... Spookier Than Ghosts! :ghost:

It seems a little bit scary, that the return result of an arbitrary function could be something that when you assign it, gives mysterious properties.

So I don't think arbitrary functions should just cascade these things around.

Hence I'm skeptical that things like this should work, at least not out of the box:

 my-alias: lambda [var] [alias var]

It's a little hard to say that the reason that it wouldn't be allowed would be because of typechecking. I can't really see the concept of carving out these weird antiform GROUP!s and saying "that's not a PACK!"... or.... "that's not an ANY-VALUE?".

And they should probably have to obey the general laws of lifting and unlifting, so that you can pipe the states around. UPARSE depends on it, for things like the GROUP! combinator to be able to pipe things to the SET-WORD! combinator.

e.g. this seems it should be possible:

>> parse [a a a b b b] [let x: some 'a, let y: (alias $x), y: some 'b]
== b

>> x
== b

So I kind of imagine there'd be some kind of bedrockable marker you could use to mark functions as capable of producing undecayed states (similar to "vanishable" for making ghosts disappear).

What Should The States Be?

I picked ~(^foo)~ and ~(^foo.bar)~ for the aliases because it didn't seem to make sense to take the prized "word! space".

In fact, the word! space could give us something very interesting... clean-looking undecayable values. They'd be like cheap variations of the unstable ERROR! antiform.

>> var: ~(hot-potato)~
** PANIC: Undecayable PACK! ~(hot-potato)~

It's a little bit unfortunate that these look similar to SPLICE ~[cold-potato]~. And perhaps a bit unfortunate that for quite a long while up to this past week, splices were GROUP! antiforms (I still have lots of posts here to correct, as I see them)

However, if you're going to say "people can't tell the difference between ~(...)~ and ~[...]~" they probably have a bigger problem in the language, as (...) and [...] are also different. :face_with_diagonal_mouth:

But I've wanted something like this, e.g. to denote unreachable code.

switch type of data [
    integer! [...]
    block! [...]
    ~(unreachable)~
]

That's slick. I think that if you were forced to use a more decorated type, like ~(#unreachable)~ it would lose its luster. But if you weren't forced you could still chose word spellings that stood out more for your purposes:

switch type of data [
    integer! [...]
    block! [...]
    ~(=bad=)~
]

switch type of data [
    integer! [...]
    block! [...]
    ~(--fail--)~
]

switch type of data [
    integer! [...]
    block! [...]
    ~(DEAD_END)~
]

I actually kind of do want to call it a hot potato, it's kind of rounded.

>> ~(unreachable)~
== \~(unreachable)~\  ; antiform (pack!) "bedrock: hot potato"

Should "Blackhole" Be A WORD!, or BLANK, COMMA!?

When you say:

for-each [x _ z] [1 2 3 4 5 6] [...]

There is actually a slot created for that middle blank in the object made to represent the iteration. Otherwise it would be difficult to encode the notion of "skipping"...the easiest way is just to make a slot, and have writes to it be a no-op.

It used to be the variables were written to but just never observed. They stored the state. But that meant they kept things GC live (and could do so indefinitely, if the iteration object had references leak).

So with bedrock states I thought this should be a blackhole. I wondered if that should be the word ~(blackhole)~ or something like ~(_)~ ... but pursuant to what I say above, words should be reserved for the bedrock states that you can never assign.

It might seem that ~(,)~ with a COMMA! would be a better choice than space, because it's a unit type...so it wouldn't be implying a meaning for all the other potential ~(#rune)~ bedrocks. But this may be useful for something else. I've spoken before about a "true unset" though exactly how this applies I don't know.. but if it existed, it would probably need to be the COMMA! dual.

So space may be the best choice here.

Do All Setters/Getters/Accessors Store FRAME! ?

If you're going to write a single "accessor" function that acts as a setter -or- a getter, then you either need a refinement to tell you which operation you're doing, or the argument has to be lifted.

If the single argument is lifted and it's null (or void, or whatever), then you can know that means GET. Otherwise you want to set to the unlifted value.

While that works, it's a bit burdensome. If I were writing a GETTER, I'd want a light notation like:

>> obj: make object! [foo: 1020, bar: getter does [foo + 304]]
== &[object! [
        foo: 1020
        bar: 1324  ; getter
   ]]

>> obj.foo: -4

>> obj
== &[object! [
        foo: -4  ; getter
        bar: 300
   ]]

(I'm assuming that getter [...] with just a BLOCK! could act like getter does [...])

I guess it could detect the arity of the FRAME!, and if it's arity-1 assume you're prepared to take the argument as lifted.

But that's a bit unfortunate for a SETTER, which only knows setting... so why should it have to deal with lifting?

Then there's also the question of caching a last value from the getter, so that it's not called every time you get a printout in the console. The semantics get a bit weird.

Anyway Just Wanted To Get Some of That Down

I think I talked myself into the idea of all plain WORD! in the dual band as being reserved for undecayable hot potatoes.

So that means I'm not considering things like ~(blackhole)~ instead of ~(_)~, which is one thing I was contemplating...!

So this is pretty slick, but I do want to mention that QUASI-GROUP is actually a fairly useful dialecting part, e.g. for documenting multi-returns:

foo: func [
    return: [~(integer! text!)~]
    ...
][
    ...
    return pack [1020 "hello"]
]

This means that every time you see a QUASI-GROUP!, you can't assume nefarious intent.

It's just in evaluative contexts, it seems it could serve an interesting purpose. As with everything else here, the dialecting means you can't assume one part will do the same thing for all time.


The natural "this will stop code" almost makes me wonder if it should take the role of the ... in examples:

switch type of data [
    integer! [~(...)~]
    block! [~(...)~]
]

If we took to writing that to mean "nothing here" in examples, then it would free up ... to be used in actual code.

Bedrock states seem to be able to address this...

We'd use a bedrock state that doesn't correspond to any in-band Value. The ACCESSOR would test for this state, and if so have it mean "no parameter, so it's a GET and not a SET".

Let's call this bedrock state END and say you can test a variable for it with END?.

Here's a basic accessor that just stores and gets whatever value you put in it, as if it were a regular variable:

var: accessor func [^value] {
    store: static []
    if end? $value [  ; it's a get
        return ^store
    ]
    set store ^value
    return ~  ; assuming system ignores return for SET, (var: x) is always x
}

Very Cool :smiling_face_with_sunglasses: ...but Obvious Questions Arise...

You might be wondering "what if someone wants to write an ALIAS into an accessor?" Or an END? Or some kind of typechecked value?

:thinking:

I think the answer has to be "you can't do that... this is why it's called BEDROCK."

If you try to write a typechecked value or an alias into an accessor, you've wiped out the existing bedrock and laid down new bedrock.

That's not to say you couldn't build backchannels into things like your ACCESSOR. Maybe it doesn't actually have meaning for PARAMETER!, and assumes if you pass it one that means you want to add typechecking:

var: accessor func [^value] {
    store: static []
    if end? $value [  ; it's a get
        return ^store
    ]
    if parameter? ^value [
        constrain value $store
    ] else [
        set store ^value
    ]
    return ~
}

So now if you did var: make parameter! [integer! block!] it wouldn't actually assign the parameter, but would type constrain it. :-/

That seems pretty dodgy. I think a better way would be another backchannel, like:

var: accessor func [^value :constrain-type] {
    store: static []
    if constrain-type [
        return constrain value $store
    ]
    if end? $value [  ; it's a get
        return ^store
    ]
    set store ^value
    return ~
}

Then you could write a coordinating function:

constrain-var: proc [var type] {
    action: first unanti undecay var  ; gets FRAME! for bedrock accessor
    action:constrain-type type
]

Now you can call constrain-var $var [integer! block!]

This general style of communication seems like a better plan to me than distorting the meaning of SET.

I'm cautiously optmistic. This looks pretty good.

There will probably be some kind of "Bedrock pecking order", e.g. writing typechecking information onto an alias might be okay to follow through and make the aliased variable typechecked. But you wouldn't want writing an alias onto a typechecked cell to reject the alias attempt because the alias was an invalid type.

We'll just have to see what sort of things you can and can't do with it.