The Canonical List of Non-Negotiables

I realized this is something that maybe hasn't been attempted, but would be very useful. That is a list of concrete code samples where if Rebol won't run that code looking that way, then it is dead to you.

I'd imagine it's easier to say things like "it's dead to me if it requires me to install Java". But there must be something other than the meta-properties of the language which people consider foundational? Actual code--looking and acting an actual way?

I'm not looking for controversial things here--rather things generally agreed on as good. But naming some of them out loud might have benefit. I'll start.

COMPOSE, UNSPACED, ETC. VAPORIZING CONDITIONALS

The lack of conditional vaporization bugged me to no end about Rebol2:

rebol2>> compose [<a> (if false [<b>]) <c>]
== [<a> none <c>]

rebol2>> rejoin ["a" if false ["b"] "c"]
== "anonec"

You had to throw in an EITHER with an empty branch, to get an UNSET!.

rebol2>> compose [<a> (either false [<b>] []) <c>]
== [<a> <c>]

rebol2>> rejoin ["a" either false ["b"] [] "c"]
== "ac"

So I advocated vaporizing NONE! by default for a while...facing resistance from those wanting to use them as placeholders in blocks, adamant that NONE! was a value.

Hence the idea was moved around to being more like a failed conditional would return an UNSET!. But because UNSET! was "a value", it could legitimately be put into blocks.

Hence Ren-C rethought the playing field to have "non-valued" states...like NULL (which you couldn't put in blocks, but could assign to a variable) and the transient VOID state (which you could neither put in blocks nor assign variables to...attempting to do so would "unset" the variable).

So failed conditionals were re-tuned to return VOID:

>> compose [<a> (if false [<b>]) <c>]
== [<a> <c>]

>> unspaced ["a" if false ["b"] "c"]
== "ac"

The NULL state was decided to be an error in these situations. So you can't go directly from a NULL variable to a vanishing slot without something that turns it into VOID. The construct that does so is called MAYBE.

>> var: null

>> compose [<a> (var) <c>]
** Error: need non-NULL value in COMPOSE slot

>> compose [<a> (maybe var) <c>]
== [<a> <c>]

DEFINITIONAL RETURN

Novices using Rebol2 or Red aren't really clear on how their RETURN works. (Or doesn't work, as the case my be.) RETURN climbs the stack until it finds a function that is willing to accept returns.

  • Functions in Rebol2/Red that won't accept returns: IF, WHILE, or pretty much any native
  • Functions in Rebol2/Red that will accept returns: any user FUNC you write

To give a brutally simple example, you cannot implement UNLESS in terms of IF:

 rebol2>> foo: func [x] [if not x = 10 [return "not 10"] return "it's 10!"]
 rebol2>> foo 20
 == "not 10"

 rebol2>> unless: func [cond block] [if not cond block]
 rebol2>> bar: func [x] [unless x = 10 [return "not 10"] return "it's 10!"]
 rebol2>> bar 20
 == "it's 10"  ; D'oh

That UNLESS, because it's a FUNC and not a native, is a candidate for receiving RETURN. So the UNLESS itself returned "not 10" instead of returning from bar. Execution continued and the `return "it's 10!" ran. I maintain that correct behavior constitutes another must-have, and I was by no means alone in this, nor the first to say so.

>> unless: function [cond block] [if not cond (block)]  ; see note re: group!
>> bar: function [x] [unless x = 10 [return "not 10"] return "it's 10!"]
>> bar 20
== "not 10"

I do not consider annotating UNLESS to say "I'm the kind of thing that doesn't catch returns" to be remotely acceptable. I'd sooner throw out the project than go that route. Addressing definitional returns wasn't at all trivial...even though conceptually it was understood what needed to be done. It was one of the first things I tried to do in open-sourced R3-Alpha. The rearranging I had to do in order to understand the code well enough to accomplish it laid the groundwork for many features to come.

So this belongs in the non-negotiable list.

(Note: The reason you have to put a group! around block (or say :block) is due to soft-quoted branching, and I argue for the tradeoff here. I would not consider that one of my non-negotiable points for this list, though I've offered what I believe to be some compelling arguments. I do--however--consider FUNCTION instead of FUNC to be a non-negotiable way of writing this, with current leaning that FUNC is a full synonym for FUNCTION)

...and...

That's only two off the top of my head. But my idea was that we can keep adding posts to this thread whenever someone thinks of something. What has to work else it's "dead to you"? @IngoHohmann, @rgchris, @gchiu, @Mark-hi, @BlackAttr... ?

Like I say: please avoid functionality concepts like "has to talk to ODBC"...unless you have a very specific code sample that looks exactly right for how it needs to look for some case.

3 Likes

I don't have any non-negotiables at this stage of things. In the R2 past I've had minor annoyances, and I'm sure as I use Ren-C there will be things that I wish were more simple or compact.

2 posts were merged into an existing topic: The R3C Branch ("Chris's Rebol" or "Rebol 3 Conservative")

ATOMICITY OF ELEMENTS IN BLOCK!s AND GROUP!s

A new non-negotiable for Ren-C is that for any BLOCK! (or blocklike thing) the following property holds:

block2: copy []

for-each item block1 [append block2 item]

assert [block1 = block2]

Prior to isotopes, it wasn't conceivable that such rules could hold. But after years scaling a mountain of design... I concluded this could be achieved, if all the tricky behaviors came from values "at quoting level -1" that could not be put into blocks.

  • There are no isotopic actions to implicitly execute; you'd get an error trying to put them in the block.

  • There are no blocks/groups/paths that will splice into the target, because splicing requires an explicit conversion to an isotope.

  • There are no "unsets" to trip on that you can find in a block, because the state conveying "unsetness" (nihil) is an isotope.

2 Likes