"Rebol" vs. "The DO Dialect"

An interesting issue came up--which I think gets at a sort of philosophical point about what Rebol is--and perhaps suggests something about terminology...


A big novelty in the fledgling "libRebol" API is its mixtures of UTF-8 strings with values/instructions, using only standard ANSI C. Up to this point it's been demo'd under the name rebDo(). A simple example:

REBVAL *block = rebBlock("%foo.reb %bar.reb", "%baz.reb", END);
REBVAL *result = rebDo("first reverse", block, END);

(We see here variadic constructs, which treat the C string literals as LOAD-able data. So "first reverse" is interpreted as two WORD!s...if you wanted a string you'd have to say "{first reverse}")

So...when you read this, what do you expect result to be?

Don't scroll down and read more until you've thought about it. The point of me writing this is that I think it's a more profound question than it appears to be on the surface.

.
.
.
.
.
.
.
.
.
.

I'd been assuming the result would be the FILE! value %baz.reb. In other words, I was interpreting the intent as if rebDo(...) was implicitly rebDo("[", ..., "]")

block: copy [%foo.reb %bar.reb %baz.reb] ;-- e.g. rebBlock()s are mutable 
do [first reverse block]

The alternative interpretation would use the DO native to execute the file as a script. This would mean seeing the intent as:

block: copy [%foo.reb %bar.reb %baz.reb] ;-- e.g. rebBlock()s are mutable 
do (first reverse block)

Looking at the other functions we're talking about exposing variadically, assuming a BLOCK! doesn't make a lot of sense. For instance:

if (rebNot("error?", value, END)) {...}

Because you wouldn't want that to act like:

if not [error? value] [...]

That would never run the code! It's much more likely you meant:

if not (error? value) [...]

So here's where we get into a terminology question:

"What do you call a rebXxx(...) where it acts like what DO would do with a block?"

There was a discussion at some point where Carl said he believed the evaluator's behavior was "The Rebol Language" itself--not "the DO Dialect of Rebol". And so that kind of gets into that point. DO isn't involved. The code is just getting...Reboled?

Because of the EVAL native, and its special purpose as the rebEval() "instruction", this can't/shouldn't be called "rebEval".

My leaning of the moment is to call this rebRun(). There's no meaning today for RUN "in the box", and although I've lobbied for the idea of making the most of short words, I've also suggested that maybe leaving a few to the users could be okay.

So here's where the terminology point comes up. You're not "DO-ing code"... DO is a command that, given certain parameterizations, Runs Rebol Code.

Pursuant to this--and in the interest of cutting down API entry points--I don't know if there really needs to be a rebDo(). rebRun("do", block, END); seems less ambiguous, having rebDo(block, END); seems to add chances for a misunderstanding. We'll see.

I have been fairly convinced that DO is a dialect that defines the default/host Rebol language evaluator and that each dialect is a Rebol. I see that Rebol is the toolkit, the "lego blocks" as you put it, that allows a variety of languages (dialects) and interpretations. From this perspective, I disagree with the idea that there is one type of evaluation that is Rebol. It's why I see Ren-C as Rebol and why I expect future interpretations to be Rebol. Others may vehemently disagree, looking at a specific interpretation as being true Rebol, I see that as too narrow.

So I'd like to see the context of Carl's quote - I'm surprised by it. I think there's more to Rebol than what we've seen so far.

I note that DO and REDUCE are symbiotic. One can view REDUCE keeping the results of a sequence of DO's (/next) and one can view DO/next as a single expression reduce.

I've run out of time here, but on the above basis I'm not sure what the problem is with "Do", but I confess that I haven't thought through the semantics of the C api and what would be a great experience for a C programmer using it.

I gave the motivating example, though perhaps it was too long-winded. More briefly:

  • If there is something called "rebDo", there needs to be a behavior given to rebDo("%foo.reb", END)
  • Is this more like saying do %foo.reb or do [%foo.reb]?
  • Either meaning you pick, there needs to be a way to accomplish the other intent:
    • If you pick the first meaning, it complicates the second (more common) case, so you have to say rebDo("[", a, b, c, "]", END); or rebDo(rebBlock(a, b, c, END), END) for typical calls.
    • If you pick the second meaning, accomplishing the first would be done with something like rebDo("do", "%foo.reb", END);, where the repetition of "do" does not seem like good mojo.

What I'm hoping to avoid is a situation where the API has too many entry points, recreating the language in C. So I don't want to see rebDoString() and rebDoFile() and rebDoBlock()...and then have this pattern being repeated for every APPEND or ANY or other function you can think of.

But it is tricky. If you give the C programmers a rebFail() function, they probably don't have an already-formed ERROR! all that often. More likely they'd prefer the variadicness to act like elements of a BLOCK!. That's okay, because if you want to fail on an error directly you could say rebRun("fail", error_value, END); But the point I'm making above is that if you call "rebRun" instead "rebDo", you wind up with the rebDo("do", ..., END) double-do situation.

So it's maybe a bit unique. While you could document rebReduce() or rebFail() as having an implicit blocklike behavior for their arguments, calling DO the means of "stepping out to the pure evaluator" ignores the fact that the DO native is itself a higher-level composite than the pure evaluator.

That's the line of reasoning I'm looking at. If you don't have a problem with rebDo("do", some_file, END); being the way to run a script, and that rebDo(some_file, END); just gives you the file back as it is, I suppose it's okay. But I find that confusing, myself.

For me to use the api it will be from something other than C, probably VB or C# .NET.

Will the C api maintain it's intended elegance when being called from these other languages?

Variadic-wise, C# is higher level, so you can do more. You don't need the END (it can be made unnecessary in C99/C++ as well). You could take integers or other classes. But it's another one of these languages that uses two-bytes-per-character in UCS2, and I don't even know if there's a way to get at that memory directly.

I don't really use C# or Java-ish things, but if you were trying to mimic libRebol and not get too idiomatic, I tried a couple things in https://dotnetfiddle.net/, and it produces:

UTF8 bytes: 3130202b
REBVAL: Integer@20

Which is the kind of transformation steps needed. Source:

using System;
using System.Text;

public class REBVAL {
	string whatever;

	public REBVAL(string whatever) {
		this.whatever = whatever;
	}
	
	public override string ToString() {
		return whatever;
	}
}

public class reb {
	public static REBVAL Integer(int i) {
        return new REBVAL("Integer@" + i);
	}
	
    public static REBVAL Do(params object[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
			if (list[i] is String) {
				byte[] utf8 = System.Text.Encoding.UTF8.GetBytes((String)list[i]);
				
				StringBuilder hex = new StringBuilder(utf8.Length * 2);
				foreach (byte b in utf8)
					hex.AppendFormat("{0:x2}", b);

                Console.WriteLine("UTF8 bytes: "  + hex.ToString());
			}
			else if (list[i] is REBVAL) {
				Console.WriteLine("REBVAL: " + list[i].ToString());
			}
        }
        Console.WriteLine();
		return new REBVAL("Sum of");
    }	
}

public class MyClass
{
    public static void Main()
    {
	    REBVAL twenty = reb.Integer(20);
        REBVAL sum = reb.Do("10 +", twenty);
    }
}

That's just trying to reproduce the API verbatim(ish). You could also get fancier, with the likes of reb.Do("10 +", 20) or REBVAL twenty = 20 or such.

But yes, you see the same semantic problem come up. Would you want this to be do (10 + 20) or do [10 + 20]?

Note that do 30 is an error in Ren-C...not 30 as in R3-Alpha or Red. (I think such a "I don't know what DO-ing this means, so pass it through" is an extremely poor invariant.)

With more time, your first example:

REBVAL *block = rebBlock("%foo.reb %bar.reb", "%baz.reb", END);
REBVAL *result = rebDo("first reverse", block, END);

The alternative version looks correct:

do first reverse block

which reduces to:

do %baz.reb

"What do you call a rebXxx(...) where it acts like what DO would do with a block?"

Looks like what happens when you type something into the console's repl. So as REPL stands for "read, eval, print, loop", one could call it rebRE, but rebRun is probably nicer.

As you point out rebRun is not Do-ing code, it's scanning and evaluating, so rebRun is not rebDo.

With rebRun in mind, the above example could be understood as rebDo calling rebRun as a convenience to the C programmer with the result of that used as the argument to the underlying Do. But as you point out, that's over the top. At the cost of scanning the "do" there's no need for rebDo.

If there is something called "rebDo", there needs to be a behavior given to rebDo("%foo.reb", END)
Is this more like saying do %foo.reb or do [%foo.reb]?

With rebRun:

rebRun("%foo.reb", END)

would return like the console:

%foo.reb

and if rebDo still exists:

rebDo("%foo.reb", END)

would be equivalent too:

do %foo.reb

rebRun as above, allows the programmer to think to the console for guidance for constructing the expression.

In my mind: "How can rebDo exist alongside rebRun - how does one deal with the refinements? If a C programer wants to call Do, is a frame the way to do it?"

Though, I don't think I was that unclear the first time around, you were just in a hurry. :slight_smile:

Well, yes, now we're on the same page.

But it does make one take a bit of a step back about how we word things.

Just because it crossed my mind, and I'm not sure how useful it is as a Rebol construct, but it's more a "what-if":

run ["print reverse" copy "{string}"]

So this imaginary "run" command would actually splice pieces together to execute them. Clone the APIs behaviors but inside of Rebol. WORD! could mean get variable actively, GET-WORD! could mean get variable inertly, GROUP! could mean evaluate code and use result evaluatively, BLOCK! could mean execute code and use result inertly.

I didn't say it was a great idea, just something I thought of.