Why Have Both BLOCK! and GROUP!

Maybe this is a stupid question, but here’s something I’ve been wondering about Rebol: since quoting exists, block!s feel a bit redundant. Something like [a b c] could just as easily be represented as the (a b c). So, why do both exist?

Or, to put it another way, it feels like Rebol has two orthogonal concepts:

  • Grouping: you can take a bunch of symbols and parenthesize them to get a group!
  • Quoting: you can take a value and quote it to prevent evaluation

So it makes sense that you can combine these: the (a b c). Except that Rebol doesn’t seem to take advantage of this fact at all — rather, it goes the opposite route, by creating a new data structure which conflates aspects of both of these.

(Note: Ren-C calls (...) a GROUP! instead of a PAREN!)


Rebol specifically leverages a lexically unique inert type of BLOCK! as part of its core language.

if 1 < 2 [print "Message"]

If there was only GROUP!, then you could get past this with quoting:

if 1 < 2 the (print "Like this")    ; Red/Rebol2 would use QUOTE here

if 1 < 2 '(print "Or like this")    ; Red/Rebol2 don't have quoted GROUP!s

But that's not very nice looking, and it doesn't have the same visual cues to readers. The inert and non-inert portions all look similar. Plus it means you can't give alternate meanings to quoted groups.

Key to Rebol's value proposition is to give you a box of parts that you can apply how you wish in dialects. Checking for things like [ matching up with ] and nesting properly with () is given to you for free. And you can make them do different things.

Consider in PARSE, where BLOCK!s are used to group rules, and GROUP!s hold executable code:

>> parse "aabab" [some ["a" (print "Found A") | "b" (print "Found B")]]
Found A
Found A
Found B
Found A
Found B
== "a"

And note that quoted groups (or quoted anything) have meaning for recognizing literally when parsing structure:

>> parse [(A) (B) (A)] [some ['(A) (print "Found A") | '(B) (print "Found B")]]
Found A
Found B
Found A
== (A)

There's nothing you couldn't do here with fewer parts and being more verbose. But a lot is lost.

parse ... '(some '(literal (A) run (print "Found A") ...

"Beware of the Turing tar-pit in which everything is possible but nothing of interest is easy."

See also the quote from Douglas Crockford:

"Rebol's a more modern language, but with some very similar ideas to LISP, in that it's all built upon a representation of data which is then executable as programs. But it's a much richer thing syntactically."

"Rebol is a brilliant language, and it's a shame it's not more popular, because it deserves to be."

—Douglas Crockford, founder of JSON, 2009 [link]

1 Like

Oh, I wasn’t asking about the specific syntax […]. It would be pretty easy to define […] as syntax sugar for '(…). What I’m asking is: why was this pathway not chosen? What purpose does it serve to make ‘block’ a fundamentally different concept to ‘quoted group’?

If I quote a group, I mean I want to quote the group, and I want it to look that way:

>> append [a b c] (1 + 2)
== [a b c 3]

>> append [a b c] '(1 + 2)
== [a b c (1 + 2)]

And if I append a block, I want it to be appended that way (it will be whether I quote it or not, as the quote is discarded under evaluation in any case.. though in modern evaluation, any quoted material doesn't have its binding affected.)

>> append [a b c] [1 + 2]
== [a b c [1 + 2]]

>> append [a b c] '[1 + 2]
== [a b c [1 + 2]]

This would only be obfuscated if blocks were a rendering trick for quoted groups... and it's not clear that quoted BLOCK! would be possible at all (or perhaps double quoted GROUP! wouldn't be possible).

And quoted blocks are useful (to this day, Red adherents don't understand what use a quoted BLOCK! would be, since they don't think of blocks as "evaluating".) But continuing the example above:

>> parse [[A] (A) [A]] [some ['[A] (print "Block!") | '(A) (print "Group!")]]
Block!
Group!
Block!
== [A]

And more generally, it's useful to be able to quote something when you don't know whether it's evaluative or not, and are splicing it into a stream of evaluation, if you don't want evaluation. Several sophisticated features in Ren-C and the API depend on the generalized ability to quote--even inert things.

For background... historical Rebol/Red doesn't have any general concept of quoting at all. It has LIT-WORD! and LIT-PATH!, that simply have the evaluative rule of producing WORD! and PATH! respectively. Calling a BLOCK! a LIT-PAREN! and rendering it with brackets certainly didn't occur to anyone at that time.

1 Like

OK, this is fair. The way I suggested would still be self-consistent, but the syntax sugar would make it confusing:

>> append [a b c] '[1 + 2]
== [a b c (1 + 2)]

(In Lisp this isn’t a problem, since it would be printed '(a b c (1 + 2)). You could do that in Rebol too, but like you said the […] syntax is very nice to have.)

In general, I suppose this is one of the things which makes Rebol such a different paradigm to Lisp. In most Lisps, everything starts out its life as code, and macros let you intercept and modify the parsed AST before it’s evaluated. This makes syntax sugar trivial, since it merely modifies the AST some more. But in Rebol, it’s more idiomatic to create bits of data, then evaluate them as code. And this makes it difficult to introduce syntax sugar, since it’s desirable for that data to be able to store any code verbatim. (On the other hand, it makes dialecting much more natural, for the reasons you mentioned.)

(Incidentally, the article which originally started me along this train of thought was yours on Rebol vs Lisp macros, so thanks for writing that!)

This feels very similar to the way I was thinking about it, especially this line you quoted:

blocks are already un-evaluated by default. The non-quoted version would be paren!

My objection was a little different to theirs though. I agree that, if we have quoting at all, it’s perfectly sensible to allow for quoting anything, including blocks. My question was: if we have quoting, why do blocks exist at all?

I suspected it might have been something like this. But, since Ren-C is willing to rethink so much of Rebol, I was wondering why it didn’t rethink this as well.

1 Like

Further on this point: note that when you use variables, that "shields you from evaluation"... in the sense that the variable fetch uses up the evaluation, giving you the unevaluated result of the variable contents:

>> animal: 'zebra
== zebra

>> if animal = 'zebra [print {It's a zebra.}]
It's a zebra.

But when you are using the API, you don't have this step.

REBVAL* animal = rebValue("'zebra");

rebElide("if", animal, "= 'zebra [print {It's a zebra.}]");

The animal is a C variable, not a Rebol-variable as WORD!. Hence it's spliced into the code as-is without protection via its "name". So it's as if you wrote:

eval compose [if (animal) = 'zebra [print {It's a zebra.}]]
=>
eval [if zebra = 'zebra [print {It's a zebra.}]]

So you'll get something like a zebra-is-undefined error, because it will look up the first zebra as a WORD! variable under evaluation.

An advantage to arbitrary quoting is that you can get that same protection you would have gotten from a variable reference on a value regardless of what it is. Ren-C's COMPOSE can transfer quoting or quasi levels from GROUP!s onto the spliced items:

eval compose [if '(animal) = 'zebra [print {It's a zebra.}]]
=>
eval [if 'zebra = 'zebra [print {It's a zebra.}]]

In the API, this can be done with a quoting instruction:

rebElide("if", rebQ(animal), "= 'zebra [print {It's a zebra.}]");

Regardless of what animal is (INTEGER!, GROUP!, BLOCK!, something already QUOTED!, etc.) you get that one step of quote, and you may not know if you need it or not. This is just another facet beyond the applications of quoting to arbitrary meanings in dialects.

Note that Ren-C's COMPOSE has some other tricks up its sleeve, e.g. transferring colons onto things that support them:

>> compose [(animal): "not a good pet"]
== [zebra: "not a good pet"]
1 Like

Actually, you know what, I’ve just noticed that a situation like this exists already:

>> [{a b c}]      
== ["a b c"]

So… if we’re fine with this, why are we not fine with ['(a b c)] being printed as [[a b c]]?

(One possible answer is that blocks are a lot more common than strings. Which is fine, but feels a little unsatisfying.)

Oh, this is clever! And an additional justification for GET-GROUP! and SET-GROUP!.

It's not just a matter of printing...

The behavior of quoted items is that they drop one quote level under evaluation.

But we don't want blocks to have evaluator behavior. They evaluate to themselves.

>> [a b c]
== [a b c]

This is integral to their character. So if they were simply a rendering of '(a b c) then we'd expect them to drop the quote under evaluation:

>> [a b c]
== (a b c)   ; if [a b c] was '(a b c) under the hood
1 Like

A-ha! This is the kind of fundamental reason I was looking for. Thanks!

(And thanks more generally for your patience with me, too.)

And on reflection this makes perfect sense… you want to be able to do things like, say, COMPOSEing one block into another:

>> code: [print "It works!"]
== [print "It works!"]

>> composed: compose [if 1 = 1 (code)]           
== [if 1 = 1 [print "It works!"]]

>> eval composed
It works!
; first in pack of length 1

But if BLOCK!s were merely quoted GROUP!s, then under assignment they would decay to a GROUP!, and then COMPOSE wouldn’t do what you want.

(Again, I’ll link this to the way Rebol works — its ‘macros’ take care of evaluation, so you need to be able to pass BLOCK!s into functions, however deep they’re nested. By contrast, Lisp has true macros which take unquoted arguments, so dropping one quoting level isn’t just acceptable, but actually vital. It’s always interesting to retrain one’s brain to a different paradigm!)

2 Likes