Every Thought On Array Splicing Has Been Had 🤯

Important Updated Response to the topic of this 2020 Post:

Nope. I was wrong. Quoting levels of -1 was a thought that hadn't been had in September 2020.

(Because isotopes in their most primitive form weren't proposed until November 2020, and it took some time before ^META conventions were laid out.)

Just goes to show you... there's always room for more insights. You can ignore this post unless you want to go down some historical dead ends...


Here's what we "know"...

BLOCK!s (and only plain BLOCK!s) Splice By Default

This is to say that BLOCK! is The universal container ("[o]"). Splicing of the contents of a non-block is done by aliasing it as a block first...which can be done efficiently without copying memory.

It was always obvious to me that PATH!s should not splice. But I didn't have any real guidance on GROUP!, or the new types like SET/GET-BLOCK!/GROUP! (and whatever @[block] or @(group) ultimately gets called). It was a toss up between "anything that uses spaces to separate elements splice, and anything tighter--like tuples/paths--not" vs. "the logo is [o], so anything with brackets is special" (I was pretty sure "do whatever Rebol2 did" was not a good idea).

But I think there's a better narrative for justifying this solution. Looking at other languages like Haskell/Rust/Elm, they all used parentheses for tuples...where their notion of a tuple is fixed-size with elements that aren't necessarily of the same type. This led me to think about the question of if BLOCK!'s universal containerness might make it something you don't just pick automatically...you'd pick something else when splicing wasn't what you'd typically want.

Here's a made-up example:

add-new-products: func [product-list: [block!]] [
    product1: '(#WID-0304 "Super Widget" $1.99)
    product2: '(#WID-1020 "Super Widget Plus" $2.00)

    append product-list product1
    append product-list product2
    append product-list [
        (#WID-0421 "Super Widget Premium" $2.01)
        (#WID-9999 "Super Widget Ultimate" $102,003.04)
    ]
]

See how light the generic quoting made the GROUP! literals? Now this makes the choice of BLOCK! vs. GROUP! something that you can reason about better. You can take advantage of the difference so you're not fighting the behavior--but "synergizing" with it.

But also...with @[...]/etc that doesn't splice, you have more options for shifting your data's conception of itself as it is passed around.

I'll point out that since there's no @ or : in the logo, this does ultimately dovetail nicely with "what's in the logo is special"...it's not just any bracketed thing, it's plain BLOCK!. :slight_smile:

"Modal Parameters" are the Answer for Anti-Splicers

I've been trying to embrace @rgchris's concept that the language bias should stick to making it so that "common" code does not lean too strongly on symbols to convey meaning. While you can retrain yourself to comprehend pretty much any symbol soup, letting your mindset drift to that "new normal" isn't good for communicating code to others. (And it's probably not good for your own ability to see clearly, either...even if you -think- you understand what you're doing.)

This has to be balanced against many other design factors; to which I'm sensitive because I actually understand what it takes to make things work at a mechanical bits-and-bytes level. So this pushes back and forth.

Modal parameters give an easy mechanism to library authors, or others who want a rigorous way to append values "as is" without typing /only at every callsite.

>> append [a b c] @[d e]
== [a b c [d e]]

>> item: @[d e]
>> append [a b c] item
== [a b c @[d e]]  ; modality comes from parameter, not fetched value

>> append [a b c] '@[d e]
== [a b c @[d e]]  ; quoting suppresses modality, then evaluates away

 >> do compose [append [a b c] '(third [<d> #e @[f g]])]
 == [a b c @[f g]]  ; quoting in COMPOSE is there to help in cases like this

It's not something that has to be brought up in early tutorials. But I like it. And it's a generic mechanism that people can use when they want a parameter to indicate the mode of a refinement...so there's generic uses for it.

Further changes to APPEND and such will be abandoned

I experimented with making APPEND only accept blocks, and then maybe blocks and strings, and other kinds of tweaks. They weren't worth it.

What's changing the game here is making BLOCK! the only type that splices by default.

It used to be that when you had a moment of doing an append of some parameter--that wasn't a block before but suddenly is now--you groaned and said "why'd I forget the /ONLY" or "why does this darn thing behave so randomly".

Now a new thing you can ask in many of these moments is: "Why was this value a plain block if I didn't want it to splice??" It should feel less random, when you have more alternatives. I'm going to look at my array choices in this new light, and maybe need /ONLY less often as a result.

Modal parameters are good to have to point people to who aren't on board with splice-by-default. And I'm willing to accept the burden of using a modal parameter to enter an @ symbol when I want it. It saves significant evaluation time over APPEND/ONLY, and doesn't require a series node allocation to hold the APPEND and ONLY words.

BLOCK! Conversions Needed

Because the only way to get splicing is now to have a block, it raises the question of how to get blocks.

AS BLOCK! is cheap; it doesn't allocate any memory, it just aliases the series as a different cell class. So it's a good choice if you know what you have is an array.

>> group: '(d e f)
>> append [a b c] as block! group
== [a b c d e f]

If you don't know if you have an array value or not, this is a little harder. But we can actually turn items into a one-cell BLOCK! without allocating any memory. This is a new trick which was called mirroring, but the mechanism is changed to where it needs a new name. The new block is read only, but that is okay for the purposes of this append since it's gone after the splice.

The name for the operation I had in mind was BLOCKIFY (though it's not using the mirroring mechanism at the moment, only the PATH! trick is, which proves it does work).

>> value: '(d e f)
>> append [a b c] blockify value  ; [d e f]
== [a b c d e f]

>> value: 1020
>> append [a b c] blockify value  ; [1]
== 1020

But that's a weird looking word, and I hate having to have lists of things like this (groupify? set-groupify?) The concept from the other day of FORCE might be interesting

append [a b c] force block! value

That's at least generic, and it kind of conveys "I want a block, if it's a block then great, if not then change it". But that makes it sound like its changing the input value to be a block, vs. wrapping it.

But maybe AS can wing it, and say that if you give it a non-block it can just do the wrapping in a block anyway and give you something read-only? :-/ I mean, the user doesn't know that every single-valued item doesn't secretly live in a 1-element array in the implementation...it might. Hmmm.

Anyway, this name looks like the only missing piece. What do you call something that doesn't do any memory allocation but just makes a light 1-element wrapper that can live in a cell, to keep us from having to add more crazy refinements?

It's not a super high priority as most cases are known to be AS (in the form known today)

Speak Up or Hold Your Peace

Like I say, I think I've probably had all the thoughts. There aren't any more to be had. If you want to prove me wrong, post it here...but do it soon.

2 Likes

It is a kind of CAST that you want to do here. Could call this FORGE. Other suggestions FABRICATE (too long) HATCH, CRAFT (I like that), FEIGN, FIDDLE.

Do as R2 did, is a good starting point. Many things have been looked at with a refreshing mindset. Though a lot of things have grown to become as they are, which is not always as consistent or well-thought out. Imo all is open for improvement with the proper argumentation.

I think I like where this is leading to and being able to do without the /ONLY to append a block! as a block! to another block! is pretty cool. I have had many occasions where I forgot the /ONLY and having the @ notation to signal the /ONLY purpose is fine for me.

Another name for BLOCKIFY could perhaps be BLOCKSET, ASBLOCK, TOBLOCK.

In some sense, all of life is "storytelling" in one form or another. So a lot of this deep consideration is really about "having a story".

When people show up and ask why something is the way it is, it's important to be able to tell that story and have it be something that they can put in their mind and be comfortable with. I think due diligence has been done here, and no one can say alternatives weren't given a shot.

I'll point out that it's not just about the endgame. While it may seem like nothing is accomplished by spinning one's wheels trying to change things like this back and forth over years, that overlooks all the enhancements that came in that process.

I want to avoid "TO" usages in contexts like this because I'm pretty sure all "TO" operations need to make a new series.

>> input: '(a b c)  ; note: remember that Rebol2 INPUT is now `ASK TEXT!`

>> output: to block! input
== [a b c]

>> append input [d e f]
== [a b c d e f]

>> output
== [a b c]  ; not changed when original is changed

What we're looking for here would be more like what AS does; it's just a question of how much sense it makes to be able to say as block! 1

I've been thinking about the techniques for AS BLOCK! giving back [] as an immutable block that lives in a single cell...and it's a little bit tricky in the implementation because the cell has no room for an index. So if you say something like foo-next: next as block! then there has to be some magic.

I think the magic is that it uses a single bit in the cell to indicate whether it's at the head or the tail, and if you try to step outside of that boundary then you wind up triggering an allocation of a read-only 1-element array that gets put into an actual cell. This would mean you'd get a lot of those with:

pos: as block! <foo>
pos2: skip pos 2
pos3: skip pos 3
pos4: skip pos 4
...

Following this all through in the code, I'm pleased with the very clear definition...that has a philosophical basis now:

append block :value 
; ^-- appends 0 items if value is null, 1 item if not a block, or N if block

It's rigid, it's clear. You have an easy test of block? :value for knowing if you're dealing with the universal container. If you want to append it as-is, use append/only or @value.

Clarity Disappears If You Use TO BLOCK!

Let's put aside AS for a moment. As pleasing as the above is, you're quickly into the danger zone once you write:

either block? :value [
    append block value  ; add itemwise
][
    append block to block! :value
]

The spectrum of meanings for TO BLOCK! value is currently so variant that this is nigh-unknowable if you don't know what type value is.

But one thing is clear...it doesn't make a whole lot of sense to have your TO BLOCK! of a value just wrap that value in a block. You can do that with reduce [value]...it's a poor use for TO (and if you want blocks left as is, you can use blockify value).

I'd go so far as to say that it's so bad that no data type should define its TO BLOCK! conversion as simply putting the item in a block. That's just a way of saying "I can't meaningfully be expressed as a BLOCK!". If that's the case, the conversion should be an error.

rebol2>> to block! [12-Dec-2012]
== [12-Dec-2012]  ; I think we can now say that this is "clearly bad"

There are likely some other good rules for what TO BLOCK! would do. e.g. FOR-EACH on your item should give back individual things that match. So this looks inconsistent to me:

rebol2>> collect [foreach ch "ab cd" [keep ch]]
== [#"a" #"b" #" " #"c" #"d"]

rebol2>> to block! "ab cd"
== [ab cd]  ; e.g. it was transcoded

That TO BLOCK! is much more obvious if you just say transcode "ab cd". So I think the FOREACH result should be the TO BLOCK! result. (This aligns with how I've been talking about how TO BLOCK! of a string giving back individual CHAR!s would be handy if you are dealing with some kind of string algorithm for which fixed-size codepoint manipulation would be useful.)

My proposal for AS is an efficient alternative to TO

The goal of AS is to produce something that doesn't necessarily have its own identity (if it can avoid it) but otherwise should act the same as TO.

This means you could do some kind of surgery to build a BLOCK! you don't need for anything else, and then as path! it. Even though PATH! is no longer in the ANY-ARRAY! typeclass (not always guaranteed to have an array under the hood), the system would take advantage of it (since it can in this case) and the path would co-opt the series internally.

But...

If AS is just a compatibility for TO, then I've suggested this behavior of falling back on blocks if you don't know what else to do is not cool.

Point is that we have to use a lot of imagination to know what the heck someone means if they write:

append block as block! some-completely-random-value

The value might be a DATE! or an INTEGER! or a GROUP! or a PATH! or who-knows-what. I'm not sure where in the code anyone should be writing this and expecting it to "just work".

So I think the example is just flawed by design. You shouldn't see code like this. Instead you should see things that look more like:

append block switch type of value [
    path! [to block! value]  ; don't try to use internal sharing optimization 
    group! [as block! value]  ; do use internal sharing
    date! [compose [the-date: (value) {adds 3 items}]]
] else [
    value  ; append as-is
]

This gets rid of having to have a cheap way to put an item in a block, just to conform weird values to the "Promise me you're making a block" protocol. I think that is just a red herring.

I'm really liking how this is laid out with the single unambiguous point of control on splicing. BLOCK!-or-not. Again...its simplicity makes it something you can leverage vs. fight against!

1 Like

It's fine for me!
(some more chars)

1 Like

Let me mention another implication here, that wasn't explicitly stated, but follows naturally.

When we're trying to define what append string block means, it has to be the same as appending the elements one at a time.

block: [<<items>>]
append "abc" block

=>

append/only "abc" first block
append/only "abc" second block
....
append/only "abc" last block

...but this doesn't say anything about what APPEND/ONLY of a BLOCK! to a string means.

It just tells us that append "abc" ["d" "e"] should not be "abcd e" or similar. Because we're never appending a block to a string, we're only invoking the containership.

Yet when we actually do append a block to a string, we could change the behavior: Rebol2 did so, by FORM-ing:

rebol2>> append "abc" ["d" "e"]
== "abcde"

rebol2>> append "abc" [["d" "e"]]
== "abcd e"

But if this is so, it should be what you get with /ONLY, which Rebol2 did not do:

rebol2>> append/only "abc" ["d" "e"]
== "abcde"

With Rules In Place, How Do We Get Actual Benefit?

Now we know that blocks are the only splicing type and using /only is the same as if the input were in a block of length 1...

...this gives us guidance on our degrees of freedom, e.g. we might say GROUP!s are different and don't FORM:

>> append "abc" [["d" "e"] ("f" "g")]
== "abcd efg"

We can say that. But what gives actual benefit? If blocks are divergent between top-level block behavior and how they act one at a time, it's confusing.

Brainstorming here for a moment...but what if we reversed that, so BLOCK! acted the same if it was splicing or not, but GROUP! would space things out?

>> append "abc" '("d" "e")
== "abcd e"

>> append "abc" ["d" "e"]
== "abcde"

>> append "abc" [["d" "e"] ("f" "g")]
== "abcdef g" 

That doesn't seem quite as useful as if GROUP! appending gave you a leading space. What would that look like?

>> append "" '("x" "y")  ; base case, don't add leading space
== "x y"

>> append "abc" '("d" "e")  ; content already, so add leading space
== "abc d e"

>> append "abc" ["d" "e"]
== "abcde"

>> append "abc" [["d" "e"] ("f" "g")]
== "abcde f g" 

Now we're seeing something less confusing, that's shaping up to be more useful and consistent.

But GROUP! is probably the wrong type for this distinction. PRINT uses GROUP! to calculate data and throw the intermediate results away. Maybe a different type of BLOCK! would be better. GET-BLOCK! perhaps?

>> append "abc" [["d" "e"] :["f" "g"]]
== "abcde f g" 

Not everything has to have a behavior, and probably should not. Maybe GROUP!s evaluative bias means it simply shouldn't be allowed to be added to strings...that it's likely a mistake.

This seems to point in the direction of greater usefulness and explainability. Thoughts welcome.

It took another 6 months, but I think this is now actually ready to be tied up.

  • BLOCK! splices by default

  • BLANK! appends nothing, and appending NULL errors

  • All other inert types append as-is

  • QUOTED! will remove a quote level and append

  • Evaluative types which are not quoted will error when you append them, telling you to quote them

In the same vein as what I was suggesting before, ^ is the answer for "anti-splicers". However it now fits in with a generalized quoting mechanic, and modal parameters are no longer a thing (in the core, at least).

The most obvious side-effect is that you won't be able to append WORD!s/PATH!s/etc. without making them quoted.

>> append [a b c] 'd
** Error: evaluative value must be QUOTED! to append

You have your pick of how to deal with this. If it's literal, you could always put it in a block:

>> append [a b c] [d]
[a b c d]

If it's in a variable, you could quote it:

>> var: first [d]

>> append [a b c] quote var
== [a b c d]

And of course, there's the ^ operator...which is like QUOTE, but has some more subtlety (e.g. NULL => NULL and not quoted null (just '), and special handling of BAD-WORD! in a similar vein)

>> append [a b c] ^var
== [a b c d]

Using the standalone operator is also possible, but it's meant for expressions:

>> append [a b c] ^ var
== [a b c d]

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

I'm on the fence about what to do with GROUP!. Since they are evaluative I'm thinking that converting them to a BLOCK! to append makes the most sense.

So a few details to hash out, I guess we can let experience guide. But in terms of the big picture, I feel it in my bones that no better answer is coming.

UPDATE July 2022:

"I feel it in my bones that no better answer is coming."

That was probably just gout. Better answer has arrived.

1 Like

Though I understand anyone being skeptical given the amount of debate: I do honestly think I am zeroing in on "an answer" regarding the core ergonomics for things like APPEND. But it doesn't feel quite there yet.

The biggest point of discomfort relates to what I mentioned about WORD!s, which is proving to be a bigger deal in practice than I first realized:

(Remember of course that APPEND is not receiving 'd in this case. The ' suppresses the evaluation and "vaporizes" so that APPEND winds up receiving the d. To get APPEND to receive a QUOTED! value you'd have to have said ''d or the 'd or just d or some other such construction.)

Of course, we can just say "screw it" and allow it...and be no worse off than history (better, really). But that's small comfort, as I think history was bad!

Quick refresher:

rebol2>> value: first [d/e]
== d/e
rebol2>> append [a b c] value
== [a b c d e]
     ; ^-- not [a b c d/e]

rebol2>> value: first [(d e)]
== (d e)
rebol2>> append [a b c] value
== [a b c d e]
     ; ^-- not [a b c (d e)]

rebol2>> value: first ['d]
== 'd
rebol2>> append [a b c] value
== [a b c d]
    ; ^-- not for the Ren-C reason... LIT-WORD! decay on WORD!-fetch
rebol2>> append [a b c] first ['d]
== [a b c 'd]
    ; ^-- see what I mean?  yes this is all kinds of ridiculous

(Note: Red at least doesn't do that last one, there is no LIT-WORD! decay on word fetches.)

Point being: I want people to keep in mind just how crappy history was, in understanding why the search for better is worthwhile...especially if it kills off /ONLY.

Back To the WORD! Pain Point

Among the places I'm finding it painful to not be able to just pass a WORD!, let's look at FIND. You'll see code like:

if not find data 'd [append data 'd]

The readability suffers a bit if you change this to BLOCK!. In this example in particular, it gets harder to see the branch:

if not find data [d] [append data [d]]

If you use double quotes it seems confusing:

if not find data ''d [append data ''d]

It's likely similarly strange to most readers to wonder why you have to "quote twice" when you use the word QUOTE, but it's the generic substitute for /ONLY:

if not find data quote 'd [append data quote 'd]

And then there's JUST, which reads well but hides a lot of mechanics (it's QUOTE THE):

if not find data just d [append data just d]

I don't think this is any less learnable than /ONLY when you add it all up, and it has a mechanical rationale behind it. But there's something else...

Bigger Problem: What About OBJECT!

If you take operations like FIND and SELECT they've historically been willing to operate on BLOCK! or OBJECT!. Should the rules about not taking evaluative types apply to them, too? :-/

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

>> select obj 'a
** Error: SELECT can't take evaluative types, use QUOTED!

>> select obj ''a
== 10

>> select obj [a]
== 10   ; ???

So that's not good.

But again, on the whole: there are some things going right here with the QUOTED! behavior...and some things going wrong here with the WORD! behavior...so it's important to stop and think.

One Weird Thought: What About @WORD ?

Today, the "evaluator inert doesn't need a quote" rule means that the @xxx types--which are inert--append as-is:

>> append [a b c] @d
== [a b c @d]  ; today's behavior, whereas appending a `d` WORD! errors

But since there are some types like BLOCK! or BLANK! or QUOTED! that are called out as "special" in terms of behavior w.r.t. the likes of APPEND, what if the inert @ types were treated like QUOTED!?

>> append [a b c] @d
== [a b c d]  ; since appending [d] and 'd are special, why not @d too?

This doesn't really help you if you're trying to append SET-WORD!s or other parts to the block, because there's no @foo: type (for instance). But in the specific case of words, it helps...and that's the case where we have the issue with object keys.

How far would this go? Would @word become the universal sign of the key? Might it be what's offered as the key in FOR-EACH loops?

>> obj: make object! [a: 10 b: 20]
>> for-each [key val] obj [print [mold key mold val]]
@a 10
@b 20

I'll point out that there are some benefits to these non-evaluative types taking on a bigger role. When you COMPOSE or use them in the API, plain WORD! can be very inconvenient to handle...you're always having to throw in quotes to suppress their evaluation. Remember that when you write 'a you're getting a pass on that being inert for just that one evaluation...whoever receives it still has to treat it as a hot potato.

I'm just throwing out an idea, here. But really, I don't know. I think making QUOTED! types append their quoted argument as-is is a winner, and I believe it's fundamental to Rebol's DNA that BLOCK! splice by default. Yet I think the safety net of catching evaluative types is a way of saying "oh, you're putting together something that could be CODE!" where you can catch cases where values might be splicing blocks or throwing away their quotes on accident.


And of course we can wuss out and just say WORD! is special. After all, QUOTED! is an evaluative type and it's given special meaning in APPEND. (But that is sort of the point--since it has special meaning, we try to protect usages of the other evaluative types from being misinterpreted.)

I don't really think there's any particular value in saying that the @xxx types are uniquely able to be appended as-is since they are inert, so it kind of feels like a nice option to let them mean append themselves minus the @. However that implies that ^[...] and @[...] would act the same for APPEND... via different mechanisms.

>> append [a b c] @d
== [a b c d]

>> append [a b c] @d/e
== [a b c d/e]

>> append [a b c] @d.e
== [a b c d.e]

>> append [a b c] @(d e)
== [a b c (d e)]

>> append [a b c] @[d e]
== [a b c [d e]]  ; due to internal logic of APPEND with @

>> append [a b c] ^[d e]  ; -> append [a b c] the '[d e]
== [a b c [d e]]  ; due to internal logic of APPEND with QUOTED!

Or we can fully wuss out and say there's no protection

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

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

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

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

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

>> append [a b c] quote [d]
== [a b c [d]]

>> append [a b c] ^[d]
== [a b c [d]]

But I think this should be considered carefully.

Looking at it in isolation, the splicing of paths and tuples into blocks looks wrong.

But if it is the right thing to do, then the @-forns look best for appending as-is.

I'm actually satisfied with the idea that evaluative types must be quoted to do APPEND/INSERT/CHANGE mechanics. But it seems the FIND still throws me, on account of it being too related to SELECT at this time, and needing to take plain WORD! for OBJECT!.

Because I want to go ahead and push forward on @BlackATTR's QUERY without trashing all of his FIND calls, I've put back the ability to FIND a WORD!--specifically--in BLOCK!s for now. Other evaluative types require to be put in blocks or quoted, etc.

This is all going to take some philosophical reckoning on what the language is and what it's actually trying to achieve... and I think UPARSE is becoming a really good lens for asking that question. It's a pretty amazing piece of usermode code; I think it's probably the most epic language showcase there is so far in the Redbol universe.

Hopefully we can make QUERY be another one. :slight_smile:

1 Like

Query (at this time) is at most a few hundred lines of unsophisticated script. I think you should make whatever changes are necessary for the language and I will incorporate them as needed. I welcome it. I already need to rework every parse expression for uparse.

A while ago, @IngoHohmann made a sensible-sounding suggestion:

I pointed out that unfortunately this wouldn't have the desired behavior at the callsites... because the evaluation of the parameter would strip the quote off before APPEND saw it:

>> '[d e]
== [d e]

>> append [a b c] '[d e]
== [a b c d e]

For APPEND to "see the tick", it would have to change its parameter convention to take its argument literally...which is not desirable...or you have to use a function that adds it:

>> quote [d e]
== '[d e]

>> append [a b c] quote [d e]
== [a b c [d e]]

You Could Ask: "So What?"

We've been expressing a willingness to use an ONLY operator to signal an intent to add things "as is":

>> only [d e]
== [[d e]]

>> append [a b c] only [d e] 
== [a b c [d e]]

But optimizing that to an extreme would be hard, and weird. The more I look at it, the more reluctant I am to push the optimizations to the point of not using a series node. e.g. the "one series node" optimization is about as far as I may feel comfortable pushing ONLY.

On the other hand...generic quoting is already written, and systemically optimized, even to multiple levels (up to 3)...and does no allocations. And QUOTE will be something users are already familiar with.

Plus, you at least have the option when writing expert-optimized code to use the learnable idiom of two ticks when you have a literal:

>> append [a b c] ''[d e]
== [a b c [d e]]

Ok, But, It Makes QUOTED! Another Interfered-With Type :frowning:

Let's say you're running through a block of items that contains quoted things and want to append them somewhere...you've now got another type that APPEND chooses to muck with if you don't say /ONLY or QUOTE:

data1: [1 2 <skipme> x y 'z]
data2: []

for-each item data1 [
    if not tag? item [
        append data2 item
    ]
]

>> data2
== [1 2 x y z]  ; oops, 'z got turned into plain z

Still.. what happened to 'z would have just as easily happened to [z]. So how much worse is losing of a level of quote than losing the containership of a block?

Rebol2 messed with your BLOCK!s, GROUP!s and ANY-PATH!s if you forgot to say /ONLY. Now it would mess with your BLOCK!s and your QUOTED!s.

I'm not in love with this aspect...but just pointing out that there's been no perfect answer to the problem, yet. So having an imperfect property doesn't rule something out automatically.

Main Point: Stepping Back From The Abyss of Optimization...

Using the sunk cost of work on QUOTED! vs. making a "magic" kind of block came to me while thinking about something related.

Imagine you have a function that takes actions to run, and you want to pass that something that just returns a constant value.

One way to do this today is to make a specialization of the IDENTITY function:

>> three: specialize :identity [value: 3]

>> type of :three
== #[action!]

>> three
== 3

But it feels a shame for such an action to take up more space and load the GC than just the simple number 3 would take. So we can imagine an overlaying cell formats, and how to "lie" about being an ACTION! when the cell is really an INTEGER!.

But oh what a tangled web we weave... trying to engineer this deception has so many problems it becomes hard to count them all. You can set meta information on actions, but this integer-action has no place to put it. If you convert it to a real ACTION! on demand, then each such conversion would get a new identity...putting the inefficiencies aside, that's a semantic problem because actions are expected to have individual identities, and these just don't.

It would be much simpler if you had written the routine that was willing to take an action to also take a QUOTED!.

This makes a good argument for why QUOTED!s should answer to the same questions that an action might...just minus that one lie of "I am an action!"

>> a: func [] [return just x]
>> q: just 'x

>> do :a
== x

>> do :q  ; colon not needed, but shows same flow as when using an action
== x

>> parameters of :a
== []

>> parameters of :q
== []

I guess the point I'm trying to make is that while it might seem a bit dissatisfying to have to handle a QUOTED! or an ACTION!, it seems that the places where you would have actual trouble with it is anywhere such methods don't make sense...and those points are exactly where a "fake" optimized action would break down.

This line of thinking took me to where I thought that making an optimized BLOCK! that doesn't have the implementation character of a BLOCK! (e.g. a series node) is a mistake in a similar vein. If you demand this particular axis of efficiency, you should use the systemic mechanism of a QUOTED! to get it...or say it's not worth it.

So Does Mucking Up QUOTED!s Beat the /ONLY Option?

I'm not feeling convinced.

The only thing I think I've convinced myself of is that the fake no-node block trick I was considering is bad for the health of the system. We are making 1-element blocks somewhat efficiently, but they're not free. So I wanted to write up the thinking behind that...and share the rest in case it triggered any brainstorms.