The Return of ALIAS

Rebol2 had a strange concept of the ability to create aliases for words:

rebol2>> alias 'append "foo"
== foo

rebol2>> foo [a b c] <d>
== [a b c <d>]

rebol2>> foo: 10
== 10

rebol2>> append
== 10

(You might wonder why it takes a string, vs. being alias 'append 'foo. The problem is that if you tried that, the fact that merely mentioning words creates variables for them in historical Rebol would mean that FOO appeared to already exist as an unset variable. The alias wouldn't override it.)

Ren-C Does This With "DUAL States"

Instead of creating aliases in the symbol table, the slot for the variable stores the actual WORD! (or TUPLE!) with binding where the quantity for the variable would go.

However, that slot has its LIFT_BYTE set to the value for the "dual state" (currently 0, but it could be 255 if that made more sense for some reason).

If you multiply assign aliases, they will all get the same dual state and alias the same variable:

>> x: 10

>> y: z: alias $x
== \~(^x)~\  ; antiform (pack) "dual: alias"

>> assert [x = 10, y = 10, z = 10]

>> y: 20  ; writes to X, Z sees it also

>> assert [x = 20, y = 20, z = 20]

>> z: 30  ; writes to X, Z sees it also

>> assert [x = 30, y = 30, z = 30]

The trick with the dual band is that since packs store lifted values, anything not lifted can't decay. So it either has to panic or it has to do something else. In the case of states it understands in the dual band, it assigns those.

But it will decay by getting the value. So:

>> x: 10

>> decay alias $x
== 10

This means if you want to "break the aliasing chain" you use decay:

>> x: 10

>> y: decay z: alias $x
== 10

>> assert [x = 10, y = 10, z = 10]

>> y: 20  ; only updates Y which didn't become an alias

>> assert [x = 10, y = 20, z = 10]

>> z: 30  ; Z was assigned before the decay so it aliases X

>> assert [x = 30, y = 20, z = 30]

It Works With TUPLE! Too

This can be great for shorthanding things in code:

>> obj: make object! [field: 1020]

>> f: alias $obj.field

>> f
== 1020

>> f: 304

>> obj.field
== 304

It's New, And More Rickety Than It Should Be...

Right now it will get in an infinite loop if you alias variables to each other. I'm not sure exactly what to do about that--as not allowing aliases of aliases seems like it might be a bit heavy-handed.

2 Likes

With aliases, we could make constructs like:

import [@some-module, exported-name <as> local-name]
;
; or more brief but potentially confusing dialected Rebol-y
;
import [@some-module, local-name: exported-name]  ; not an assignment, but...

The alias could be rigged up so that accesses via LOCAL-NAME see changes in the variable that the module exported as EXPORTED-NAME. The module could perhaps even be unloaded and reloaded, synchronizing the exports. (Not being able to patch a module and reload it without exiting and restarting the interpreter was a pain point for @BlackATTR while debugging modularized QUERY.)

So it's pretty clear that by default, you shouldn't be able to write to a module's state. Since modules can be shared, I don't know if it's ever a good idea (unless you are sure you're the only client of the module?) So writing to the aliases created for imports would be a rare thing.

And it seems like you might want assignment to mean something else--e.g. "I want to take back this variable for other uses, I don't need the module export anymore." So maybe there's an alias pattern here where writing to the variable "de-aliases" it?

We might consider the nuance of:

 g: getter $var
 s: setter $var
 a: alias $var

Maybe a GETTER can only read, but errors on writes... a SETTER can read and write... and an ALIAS will read, but if you ever write it the alias is disconnected from VAR and it just holds a new value?

Or it could be (a: alias:weak $var) and then setters could be set-only, while an ordinary alias permits both reads and writes where the write will write-through to the original variable... but weak makes writes unlink it from the variable.

1 Like