Should sequences permit non-head SIGILs?

Historically I was very much opposed to SET-WORD! and GET-WORD! being in paths, because the ergonomics were bad:

red>> p: to path! reduce [first [:a] first [b:]]
== :a/b:

>> type? p
== path!

You have something that's neither a SET-PATH! nor a GET-PATH! but it has colons at the beginning and the end. It's all kinds of confusing.

Ren-C has addressed that particular can of worms with a full design for the CHAIN! sequence type, and rules for its nesting in PATH! (and how TUPLE! nests in it). So you can get these weird looking structures, but they destructure unambiguously.

As For Sigils... At The Head They Must Be Illegal

You can't put a sigil in a sequence head position, because that would be ambiguous with wanting to apply the sigil to the path as a whole.

>> to path! [$foo bar]
** Error: This needs to be an error

The only way to allow it would be if we invented some kind of lexical escaping so we could tell the difference between $|foo/bar| and $|foo|/bar ... and I do not want lexical escaping of this kind.

But Sigils At Non-Head Positions... Maybe? :roll_eyes:

There's no fundamental reason why sigils couldn't be on non-head positions in sequences. It works.

>> $a:$b
== a:$b  ; bound

Part of me says "ugh, no, it makes everything uglier, don't let people make these ugly things!"

But one very compelling reason just for the above syntax is what I've called "dialecting function calls", such as for pointing the blame to a callsite with a FAIL.

So instead of:

my-api: func [x y z] [
    if true [
        if y < 100 [
            fail:blame ["Value must be >= 100, not" y] $y
        ]
     ]
 ]

You could write:

my-api: func [x y z] [
    if true [
        if y < 100 [
            fail:$y ["Value must be >= 100, not" y]
        ]
     ]
 ]

When you look at it like that, it's hardly a monstrosity. It's letting you push the variable to blame into the chain for invoking the function, in a way that's out-of-band with other refinements.

It could offer a cheaper way of asking to fetch variables, using @ to take the place of the old "get word" concept:

>> block: [index offset pointer]
>> index: 3

>> block.index
== offset

>> block.(index)
== pointer

>> block.@index
== pointer

It's measurably less expensive (no array allocation, cheaper to naviate to on fetch). It doesn't use a GROUP! so it doesn't get in the way of COMPOSE-ing.

Think I Have To Allow It...

I'd originally thought that it would be cleaner to disallow it. But seems to me there are too many interesting uses.

As a medium, Rebol is very free, and you have a lot of power to make hideous things. So it's about using your judgment, and I think people should know when it's getting too crazy.


I Almost Hate To Point It Out, But...

This is true of quotes as well.

>> 'a:''b:'''c
== a:''b:'''c

There's an unambiguous way of interpreting that.

>> quote to chain! [a ''b '''c]
== 'a:''b:'''c

I don't love it, but, maybe there's cases where foo:'bar would be useful in a dialect, and if it's mechanically consistent I shouldn't prohibit it. :man_shrugging: I don't know.

1 Like

A major realization about the nature of what ^META-WORD!s should actually be doing has made it fully necessary to rethink things.

Initially I thought that ^foo should be a synonym for (lift get:any $foo). It was a meta-word after all... so the result of evaluating it should produce a meta-state (quasiform or quoted).

...Right...? :confused:

Wrong!

The far more useful concept is to think of it as saying "foo is a variable that utilizes meta-representation". e.g. when something is written to it, then that thing should be lift-on-write.

And then the complement to that should be unlift-on-read.

There was already an inkling of this, with assignments in the SET-BLOCK! implementation:

>> [^foo' bar]: pack [pack [a b] pack [10 20]]
; first in pack of 2 items
== ~['a 'b]~

>> foo'
== ~['a 'b]~

>> bar
== 10

But if you're going to have a "meta-read" operation, the most useful thing is to think of it as "read-meta" and reconstitute the antiform:

>> ^foo'
; first in pack of 2 items
== a

So suddenly ^xxx: becomes a powerful assignment operator, writing the lift of its right-hand side into the variable. Among the great properties it has, is that it pipes the right hand side out to the left as is... so compare:

>> xxx: lift fail "assigning an error"
== <<quasiform error>>

>> ^xxx: fail "assigning an error"
== <<antiform error>>

Hence, you get safety from error assignments! because although it did the assignment of the value lifted, the result is not lifted and you still have to have something sink that error, or it will panic!

We can talk about how lax it should be about decaying packs. Maybe you need a decoration for that, like (^xxx.~: ...) to get them. But I'm leaning to saying it's just your responsibility to say (^xxx: decay ...) if you call a multi-returning function and don't actually want its pack.

Now let's ask about the difference between these things:

obj.^value

^obj.value

^obj.^value

At each step, you have the opportunity to say "this thing is a variable that is meta-represented".

It seems pretty clear this means the meta-mark needs to be inside the slot, not outside on the tuple itself:

>> type of first [^obj.^value]
== ~{tuple!}~  ; anti

>> type of first first [^obj.^value]
== ~{meta-word!}~  ; anti

>> type of second first [^obj.^value]
== ~{meta-word!}~  ; anti

So this isn't actually all that clear. There are some competing interests.

Let's say that $foo.bar is not a TIED! tuple, but just a TUPLE!. with a tied word in its first slot.

>> to block! '$foo.bar
== [$foo bar]

Yes... we can make the evaluator behavior of such things come back as bound:

>> $foo.bar
== foo.bar  ; bound

That can be done with magic, and the magic can make it efficient. The Sigil can be living in the cell memory of the TUPLE! itself, but not report itself there...rather report itself projected onto the first element. Hence $foo.bar and foo.bar can use the same underlying memory of [foo bar] even if it gives the impression that the two would convert into arrays differently.

But this puts a burden on dialects to reimplement that magic.

Let's say you are UPARSE. And you come up with a feature that if you say @var instead of var then the @ means you want to match items literally.

>> var: [some "a"]

>> parse "aaa" [var]
== "a"

>> parse [[some "a"]] [@var]
== [some "a"]

Works great. You implement this as the behavior for your PINNED! type.

But then, you want it to work for tuples, too:

>> obj: make object! [var: [some "a"]]

>> parse "aaa" [obj.var]
== "a"

>> parse [[some "a"]] [@obj.var]
== [some "a"]

But now @obj.var is not a PINNED!. It's a TUPLE!.

One might argue that you're looking at it wrong, and if this comes up you should use a PINNED!-GROUP!

>> parse [[some "a"]] [@(obj.var)]
== [some "a"]

But I don't want to have to use GROUP!s for this. It's too common a case to make it laborious, and that applies to things like ties too... e.g. ($ 'a.b) is significantly more annoying than $a.b and you want to do that commonly.

Could It Be That Lifts, Pins, and Ties Are Different?

It could be that mechanically, lifts just belong inside sequences, because in the evaluator they serve no purpose operating on the whole of an expression.

But I'm skeptical of making this mechanical variance. The easier variance seems to be that if you get a lifted TUPLE!, you elect to run it through the normal tuple mechanics.

Really this is all subjective. If a dialect wants to interpret things as inside or outside that's their prerogative.

Optimization favors putting the Sigil on the outside. This way ^x: fits in a single cell, and there's no "magic" required to transform $a.b => a.b.

I think lifting in the evaluator has to be the oddball, making ties and pins work normally. If your dialect wants to have similar behavior, just do the same thing for lifted things that you do with non-lifted things. Functions like GET and SET will be stylized to accommodate this, so that if you pass them lifted tuples it will treat them as if the lift were on the first item in the tuple.

This means Sigils on first elements of sequences will be mechanically illegal.

As things have gestated, I started wondering about the value of $x: being a modality of assignment:

x: 10
y: 20
if 1 = 1 {  ; wrapped branch
    $x: 30  ; $ subverts wrapping of this word
    y: 40
    print ["inner" x y]  ; 30 40
}
print ["outer" x y]  ; 30 20

Compare that with "$CHAIN! just binds the chain in the current context"

>> $x:
== x:  ; bound

You can say that other ways, maybe even using the English word BIND

>> $ 'x:
== x:  ; bound

>> bind 'x:  ; proposed
== x:  ; bound

I delved into the alternate interpretation. But the problem is that if you sacrifice $ for binding CHAIN! in order to get this other purpose, you (probably?) sacrifice it for TUPLE! too.

get $obj.field  ; this would stop working how you expect

The generality of being able to say "all bindable types have $ forms" goes away. You no longer have the reliability of being able to TIE any bindable type.


Iterators Bring Yet Another Argument In

Provoked by Chris's Iterators, I came to realize there was a missing "dereference" operator.

I also realized the inert forms of @xxx hadn't been pulling their weight (besides @[block!])

This made the interpretation of @iter.field dereferencing the iterator and picking a field out of the dereferenced thing was like having an analogue to C++'s -> operator.

That more-or-less requires @iter to be inside the TUPLE!.

I've discussed the question of if there could be an asymmetry here. It seems like there could be, but having the rules be "muddy" is unpleasant.

The asymmetry appears to be unpleasant enough that (@iter).field is the price of admission. You'd also need to do that if you wanted an anchor to add a request for that to be a ^META access.

(For the moment, we'd assume that @iter itself has no meta-controllability... so it's more like a function call).


Does ^META-Assignment Belong Under CHAIN! ?

There's a kind of annoying aspect here:

 x: expr  =>  set x expr
^x: expr  =>  set the ^x expr

These relationships become structurally inconsistent if the ^x tries to live outside the chain.


Probably Settles That Sigils Live Outside Sequences

I was hoping for @iter.field to "just work". But on the plus side, when the Sigil lives on the sequence, that means @obj.iter can work. e.g. you can derefrence an iterator that lives in an object. (The common case is probably just @iter (give me the current item) anyway.)

I was also hoping for $x: thing to work as an assignment. But it's too broken to lose $obj.field, and to a lesser (but still large extent) CHAIN! and PATH!.

But [$x]: thing is still an option.