Red's Design Issues Wiki

Here's a page posted on Red's GitHub:

Red Design Questions Wiki

I'm bookmarking it here, along with some quick review notes.

Vector & matrix DSL design

No real comment, other than it's interesting that the order of the page ranks things as "most influential". While in Ren-C, R3-Alpha's VECTOR! has been removed from the core (you can build a Ren-C without it). Hence it isn't one of the built-in types identified by a byte in the header. So it's a primordial test of a custom type, and that is the main lens through which its relevance is considered.

I'm sure if they want plenty of opinions on VECTOR! they can talk to Oldes.

Parse DSL: simplify fail rule to [end skip] , break / reject to return success/failure from the loop (while/any/some) , break now as an emergency exit from the loop, bring also into parsing

END SKIP as a way to force a fail is an amusing idea. But much improvedly, Ren-C just gives meaning to LOGIC! true in PARSE as "keep parsing" and LOGIC! false as "fail here". (See below for cool ramifications of that.) So you can fail just by saying FALSE. It takes away from the ability to match LOGIC! values literally, but you can't match INTEGER! values or BLOCK! values literally without a "QUOTE". And with the generic quoting Red argues against you've got more convenience in that area, e.g. '#[true] or '[a b c] or '3.

It is good for naming reasons, too since FAIL has very specific meaning in Ren-C (way to raise errors, fail "msg" is nicer than do make error! "msg" and provides a dialecting opportunity).

The rest I'd have to look at in more detail to have an opinion on.

Parse DSL: rules with arguments

If I read this right...Ren-C has all this and more with GET-GROUP!. :boxing_glove: This evaluates much like a plain GROUP! in a PARSE...but instead of the result dynamically composes itself into the parse stream. That makes it a full superset of PARSE's unusual IF...since in Ren-C LOGIC! true means "keep parsing" and LOGIC! false means "rule fails". Thus :(mode = 'foo) gets you the continue-or-not you want. But far more open-ended than that, especially since NULL vaporizes and continues the PARSE as #[true] would. It's all kinds of keen:

GET-GROUP!s in PARSE mean execute-and-splice-as-rule

So if you want a dynamically generated rule as a function, just say :(my-rule-maker arg1 arg2) and it will be spliced in.

I consider that bit solved. But the part that isn't solved is saving you the trouble of capturing material to pass separately...kind of as if you could extend PARSE with your function acting kind of like a keyword. Imaginative pseudocode:

 >> print-reverse: parse-func [x] [print reverse x]
 >> parse "aaabcdef" [3 "a" print-reverse [to end]]

e.g. the "argument fulfillment" phase is using parse rules--and not the evaluator--to do it. But where I lean in this direction is asking about whether there is a model by which PARSE can be extensible in the same way DO of a BLOCK! is extensible... some kind of analogue to regular functions that lets you batch together "native" PARSE extensions (like THRU or INTO) along with user-added ones that speak the protocol (as if someone could add COLLECT after-the-fact).

UPDATE: This has been hit with a bullseye by UPARSE...and the problem of calling normal functions while passing them captured parse material is covered by the ACTION! combinator

 >> print-reverse: func [x] [print reverse x]
 >> uparse "aaabcdef" [3 "a" print-reverse/ [across to <end>]]

Yes, that actually works in UPARSE!

Core: open problems of the object design

Tip of the iceberg of the problems with the model. :ice_cube: While Ren-C inherited the same object-model madness, techniques like generic quoting are making inroads representationally, and things like derived binding are aiding classic problems:

"Derived binding is an interesting design that overcomes a serious weakness of historical Rebol combinatorics (where 1000 instances of an object with 50 member functions each had to make deep copies of the bodies of each function so the words in the bodies would point to the instance, so you're making 50,000 deep copies just to create those 1000 objects)."

For a simple example, see "O-Big"

Core: efficient vector arithmetic

Regarding their "Adding a number to a vector changes the vector itself #2216", I have proposed a concept whereby operations like ADD and MULTIPLY follow Rebol's "mutate by default" rules, and PLUS (infix alias +) and TIMES (infix alias *) finesse the idea of not mutating. These ideas originated in design for efficient BigNum arithmetic, which shares many of the same issues.

Devil is in the details there, but it proposes building things on a model that is foundationally mutating.

VID DSL: automatically bind (literal only?) actor & reaction bodies to the face?


Core: loop return values

Ren-C has a systemic loop return result protocol. All loops that BREAK return NULL, and there is no BREAK/RETURN. If you wish to violate the protocol, you do so with THROW to a CATCH.

This is a helpful protocol for building your own loop constructs out of other loops...without having to reach in and figure out how to hook their BREAK. Being able to tell from the outside if the loop was interrupted far outweighs the idea of BREAK/RETURN, and "heavy nulls" are an idea well worth it to ensure that pure NULL keeps its unique status for representing break.

Core: behavior of series access outside the data boundaries

I talk some about this in "Where the Series Ends" (for you young folks, that's a Shel Silverstein reference).

VID DSL: should it allow to override already defined actors (e.g. base on-down [probe 1] [probe 2] )?


Core: how to solve inelegancies and dangers of error? try , attempt and catch on arbitrary code?

As usual, it is hiiamboris asking the good questions!

UPDATE 2022: Ren-C has an answer with "Definitional Failures" :boom:

When originally writing this post in June 2020 all I said was that it was clear "that conventional error-trapping should not conflate things that arise from "typos" with explicitly raised errors".

But I'm proud to say that yet another difficult question has been resolved with ^META/isotopic solutions (which during my brief conversations with Boris he did not appreciate, and I felt the dismissive attitude to another member of the Red audience was not worth trying to push through, vs. other uses of time).

Web: how should cached versions of remote files be named (considering user-friendliness, pathname length, sanitization of invalid chars)?

Beyond the scope of current concerns, and likely not all that relevant as the main target is Wasm.

Core: should operators use the result from funcs with literal arguments? (if f: func [:x][x * 2] , should f 1 + 2 be equivalent to (f 1) + 2 ?)

Yes. I won't delve into details on this esoteric (-seeming) point...other than to say that several features I'm very pleased with depend on this behavior. (For example: soft-quoted branching.) I find no compelling points in any counter-argument worth sacrificing the related features.

Core: should percent type allow scientific notation, and should it be constrained in range?

Don't care.

VID DSL: should panel face draw a text facet on it?


Core: do we want /deep refinement for take ?

Doesn't seem like the worst idea. But I think the better angle is just to make sure that even if TAKE didn't have it, that someone who wanted it could make it easily. So features like AUGMENT are geared in this direction.

Historical questions & explanations

Arguments on why paths evaluate picked items (so-called active accessors)

It's a reasonable-sounding argument that it is "unlikely" that you want block/1 to run the first function in a block. But mechanically it's the simpler-seeming rule. I guess it folds into a large part of the sketchiness that is path-processing. :man_shrugging:

I will say that Ren-C's "get-pathing" and PICK-ing, and "set-pathing" and POKE-ing, are unified and run under the same dispatcher. So :path/1 and pick path 1 are the same, and :path/x and pick path 'x are the same, for whatever those things happen to be. There's not a separate codebase for getting paths from PICK or setting paths from POKE. Hence it's called "path picking" and not "path selection".

Command line argument parsing rules

This previously crossed my radar. If they do the work of writing test cases, sounds great. Better them than me.

Why word and a single-word path are different (despite the visual similarity)

They shouldn't be, and single-word PATH!s should be WORD!s with all spaces are. Ren-C has implemented PATH! immutability (at the "top level")...which isn't that unprecedented, as it makes them more parallel to TUPLE!. By making paths immutable, it's possible to enforce a set of rules on them at the time of their creation (e.g. no PATH!s in their top level, that aren't inside GROUP!s, no FILE!s or URL!s, just GROUP!s/BLOCK!s/WORD!s/INTEGER!s etc.).

If this sounds constraining, consider the WORD! analogy again. Making a PATH! with a PATH! in it like having a WORD! with a slash in it. When things are immutable you have a moment of creation to enforce your check and then you just don't worry about it. It works for WORD!, why not PATH!?

(You can still cheaply alias PATH!s via AS to get a BLOCK!...but the BLOCK! you get is simply read-only. With "UTF-8 Everywhere" you can also alias WORD! as a string via AS, but once again the resulting value will be read-only. You can even alias them all as BINARY!, as in Rebol2. :-P)

The idea that PATH!s are some kind of generic "ANY-BLOCK!" is not all that compelling. DocKimbel himself has complained about people wanting long or multi-line paths. If you really want to mutate a path freely and promise not to end in a bad state, then COPY AS BLOCK! your path...muck with it, and then AS PATH! it (which will mark the underlying array read only, and do an integrity check that you didn't reduce it to one word).

(Overall this ties into great work done in Ren-C on PATH!, which includes generalizing REFINEMENT! so there isn't just /a but also a/ and a//b ... a BLANK! in a PATH! is simply not rendered. The immutability of paths means that even though /a is a 2-element path with a blank in the first slot, it can still be represented in one cell and not worry about being unable to handle mutations. See Heart :heart: Bytes)

Core: :get-word function argument evaluation semantics: R2- or R3-like? (final?)

There absolutely has to be some way to literally get a symbol/value in the position, regardless of what it looks up to. I don't know if I'm in love with the notation for :x for unescapable quoting and 'x for escapable quoting, but that's a different question.

Core: how to allow maps to have none values?

Some day Red will realize how much they miss out on by not having NULL. I guess it's kind of like societies that never grokked the invention of zero.

NULL, BLANK!, VOID!: History Under Scrutiny

Core: how money datatype equality and comparison rules should work?

It's like the Joker says...

Core: what should empty any [] and all [] return?

UPDATE 2022: both Empty ANY and ALL return void. When this post was originally written it was just ALL, and ANY returned null. But it turns out to be so high leverage compared to the tiny benefit of making ANY null that we have to do it. The proof is in the examples!

1 Like

Excellent track record of anticipating and addressing the many historical inconsistencies and tarpits of rebol @hostilefork

1 Like

So 2 years have passed and I do like to check in on things, so I thought I'd look to see if they'd updated the wiki...and to update any of my own answers. (The return result of ANY [] has changed to a void isotope, so I linked to that post, but the rest is about the same.)

Here's their (Boris's) changes:

Compare revisions · red/red Wiki · GitHub

Introduce single? map, but not last? map ?

No comment, though when you start talking about enhancing features of maps I do often think about being able to get at a [key value] pair as a reified concept in its own right. C++ added some map extract features that were kind of like what I'd been thinking about.

Concretizing maps so they are made out of "pair bricks" might deoptimize them, but it might make them more useful and meaningfully enumerable.

Anyway, tangential. I don't know what kinds of operators you should be able to have on MAP! and am more concerned about the unexplained/unmotivated dichotomy between MAP! and OBJECT!.

Why none, false, true are not keywords in Red?

Ren-C has pushed the state of the art here, where ~true~ and ~false~ and ~null~ come in word forms, quasiforms, and isotopic forms. Isotopic forms of false and null are the only ones that are actually "falsey", and they cannot be put in blocks.

Explaining why this system of parts helps address classic problems is beyond the scope of this "reaction post", but I do think it's approaching about as good as the language can get.

Why x throws an error if x is unset (and more on rationale behind unset invention)

Ren-C's vocabulary is so much more nuanced that it's hard to say much here.

Not only that, I don't really have all the answers--as Sea of Words is relatively new, and is a whole new model of what "attached variables" are (that know what module they live in, but have no storage behind them).

But I think we all agree that you have to have some way to catch typos.

Why loops do not make their words local to loop body, but rely on function to do that?

We offer both options in Ren-C. If you use a plain word, you get a new binding. A new variable is created which doesn't overwrite the other one:

>> x: 10

>> for-each x [1 2 3] [print x]

>> print [x]
== 10

If you use a QUOTED! word, it's assumed you mean to use an existing binding...and no new variable is created:

>> x: 10

>> for-each 'x [1 2 3] [print x]

>> print [x]
== 3

It's better to make it a property of the symbol than a refinement (like WiseGenius's suggestion of /local). This way you can mix and match.

It's a convention that's applied multiple places, including LET.

    let [value error]
    [value position error]: transcode data  ; awkward

    let [value 'position error]: transcode data  ; better

At first it was speculative to do it this way. But I'd say it's now become entrenched as a pretty good idea.

Ren-C has knocked this one down too!

Thanks to the fact that isotopes cannot be stored in BLOCK!, you never get isotopic ACTION! from a pick out of a block.

You can only get plain actions out of a block. To run them, you'd have to say something like run block.1.

The key being you can still put the actions in blocks, you just have to remove their isotopic status, making them plain actions (or quasiforms if you want to convey they are supposed to generate isotopes back vs. be executed if encountered by the evaluation of that block).

I will admit that managing the difference between isotopic and plain actions has driven the need for new mechanisms that can be a bit daunting. But even in the early days of those methods, the advantage of terra firma of block mechanics outweighs the difficulty.