Modules vs. Rebol's Contention for Short Words

There's an aversion in Rebol scripts of putting things into sub-contexts. You see it when the math operation tan competes with the color tan.

Despite issues like this being known about since the earliest days, it's certainly telling that there wasn't some immediate backlash, where math: make object! [tan: ...] and color: make object! [tan: ...] were made. Though Red decided to made tanned the color and take tan for the math operation, they didn't follow up by making reddish. They still define red as 255.0.0, and red is not a synonym for the system object as rebol was. (Which is a bad idea, anyway.)

A Fractal of Contention

If you tried to put the operations and colors under "math/xxx" and "color/xxx" as above, you have another problem. MATH is currently purposed as a dialect using "normal" mathematical precedence for expressions. COLOR sounds like it would almost certainly be a local variable name, and you can't say color: color/tan.

When other languages run up against this, they might attack it with case sensitivity. If Rebol were to do that it would be color: Color/tan...but that's not the game here. You can pluralize things maybe... color: colors/tan, but one might also have a variable like colors: [tan red].

All text-based languages have problems like this. BUT Rebol's desire for an English-like aesthetic puts more of a premium price on short words. And its lexical uniformity makes it harder to suss out the "parts of speech" to know what the heck you are reading:

foo/baz/bar mumble/frotz fribble  ; objects? functions? refinements? (?!)

foo.baz(mumble.frotz, bar: fribble)  ; More conventional syntax is clearer

(Note: I'm now thinking that with @foo.bar being a likely SYM-TUPLE!, that the inertness of tuples may be less useful than considering them to be a field-selector operation... and even to let you add fields to ACTION!s, as you can in JavaScript. So math [1 + 2 * 3] could work, but so could math.tangent 0.0. It wouldn't be a refinement, just a member of an aggregate that also could execute in its own right. It's a thought, anyway.)

What About Getting the Brevity That You Pay For?

Very little of what I write involves colors at all (more now perhaps with the HTML console). I'm also not a huge user of trigonometry or floating point math--at least not in Rebol. The kinds of problems I'm interested in for a script language almost never require math outside of integers. I'm more likely to use RED and BLACK as data structure annotations as in a Red-Black Tree. If I ever needed colors or trigonometry, it would be task-sensitive to say whether tan should win as tangent or the color.

Over the long run, if there's going to be any sort of scalability for Rebol...things like the inclusion of colors in global scope has to be something that someone asked for.

So perhaps modules should have a kind of dialected inclusion? There could be a language for telling the trigonometry module "give me the abbreviations". You could ask for sets of things to be bound as the words with no qualification, or others to be bound through a local alias for the module.

If you have common sets of inclusions, you should be able to abstract that. Even C can do this... you can #include a file that includes others.

In any case, I think the core language needs to pare down implicit definitions. Things are moving that direction with the extensions, but those extensions are still poking things into the LIB context without being asked. I give the color and math operations as good examples of things that must be asked to be put into the main binding of the module...otherwise you get them through a name.

What About a Shortcut to a Module's "Needs" Space?

In C++, there is a way of getting out of your "local scope" and up to the global scope:

Right now, getting access to the modules list requires you to do system/modules/mod-name. But what if each module kept a small object for the extensions that it had included, and put it in something like ~?

>> color: ~/colors/tan
== 222.184.135

>> ~/math/tan 0.0
== 0.0

>> math [1 + 2 * 3]
== 7

Someone who is doing a modest amount of trigonometry might prefer to import it named trig and access it as trig. Or import them with full names, or ask the module to abbreviate the names. I'm just talking about a spectrum of options based on how much you use the thing.

Or maybe MATH itself is what binds to TAN. So you'd say math [tan 0.0], and if you use TAN outside of MATH you don't get it.

Or it may be that ~ is just the module itself, where the binding the module did at load time wasn't to necessarily bind everything that was included. e.g. there may be a module field for math which is the math module, but it would bind plain uses of math to lib/math despite the existence of that field.

Further: the idea of importing things locally could be at a function or USE granularity. If only one of your functions mentions trigonometry, why not have that one be function [... <in> ~/trigonometry] [...] If you have multiple functions that want to do this, why not make your own variant, e.g. TFUNCTION, that adds the trigonometry dependency?

Any Methodology Should Be Able To Do Redbol Emulation

These ideas sound like they may bring a lot of concerns and decisions to the average user.

But if you can abstract it to something like Needs: Redbol and have that do all the necessary work to achieve a Rebol2/Red environment, then that would be a good proof of concept.

1 Like

I know WITH isn't a universally popular idea, but if you have objects loaded with these words, you could say:

my-tan: with colors [tan]
my-math-tan: with math [:tan]

I've been using WITH as shorthand for do bind block object for a while and it seems workable, and WITH/ONLY where you need, say:

parse "foo" with/only charsets [some alpha]
2 Likes

The possibilities of TUPLE! as evaluative (in its word.x.y form) are pretty much obliterating the idea I floated about all tuples being inert. So time to forget that concept. Of course 1.2.3 will be inert, and <tag>.foo.3 and all other inert-headed tuples will be too. Your word-headed or group-headed tuples will have to pursue other other options like value: [a.b.c] or @a.b.c or 'a.b.c.

(I'm a bit sad that I couldn't come up with an evaluative strategic value for /foo/bar that solved some grand problem related to scopes or modules or something. But the consolation prize is that since I couldn't come up with anything...having such paths be inert gives compatibility with historical refinement inertness. Time to sleep easier with that resolved.)

Furthermore...

...coming back to this, I see I already talked about this very useful possibility for something like MATH being able to act as either executable, or have sub-selection like an object.

My experiences dabbling lately with other languages kind of suggests this is pretty much the only idea anyone seems to have. (Outside of hashing the AST of a function and all its dependent source in a pure functional language and referencing everything with the hash code as a timeless invariant to name functions...).

WE NEED IT. How to do it?

Perhaps objects can have a special function in them or associated with them that can be called when you access them raw...or with slashes. But if you don't have a definition of that function, you get a field selection...hence compatibility.

In fact, maybe that function could just be called ".", and operate parallel to the divide hack for making such a thing legal as a WORD! ?

math: make object! [
    .: func [b [block!] /ref] [  ; magic makes it `-dot-1-: func [...]`
        print ["processing" mold b "in MATH dialect"]
        if ref [print "...and a refinement works too"]
    ]
    tan: func [angle [decimal!]] [
        print ["calculating tangent of" angle]
    ]
]

>> math [1 + 2 * 3]
processing [1 + 2 * 3] in MATH dialect

>> math/ref [1 + 2 * 3]
processing [1 + 2 * 3] in MATH dialect
...and a refinement works too

>> math.tan 90
calculating tangent of 90

This does raise questions of what :math should do. On the one hand, people might expect to be able to pass it as a function parameter since they were able to call it. On the other hand, you might want to assign it to something else and have it act like the full deal... having math.tan available/etc.

Hmmm. Well maybe if you want the object you'd use a terminal dot, and if you just want the behavior as an action you would use get-word!

>> math.
== make object! [...]

>> :math
== make action! [b [block!] /ref]

My notion of terminal dots was that it would guarantee you that the thing you were extracting was not a function... a concept I very much like the sound of for writing library code that is handling values from wherever and needs to protect against random executions. It seems to dovetail nicely with this.

Okay, that looks awesome.

Who wants to write it? :mage: Don't come sign up all at once, now... single file, please...

2 Likes

And then you get an object in an object and here is .. a.b.c isn't that a tuple!? So that should not be allowed(?)

Also I see you need a wizard to make it. That makes it tempting to volunteer. Perhaps I'll have a go at it, using a branch on R3N github.

If you make the module name change the precedence of wanted functionality, there would be a serious problem because any function before your current could have changed this theoretically, so you need to keep setting it before doing almost anything. :frowning:
Thus having a short name and use math.functionx or math [themathematicsblockwithfunctionx] as a choice is good enough I guess.

I don't understand the concern, so please write out a concrete example if you have one.

If you got a module with functions and these can use functions from another module, in theory that function can reset the active module to its own ('WITH) in effect overriding the previous assignment. Your math function will call tan but because you cannot guarantee the 'WITH for math still holds it could be the color tan being used.

In a more practical way a low precision method could be chosen where a high precision one should be used.

First of all, that's no different than being able to change words in LIB.

Second of all, we won't have changing words in LIB be so easy by default in the future. That's the whole reason we have all the CONST and PROTECT and FREEZE functions to choose from. The module system can take advantage of this based on your settings, just as you can use it in your normal day-to-day code. Why do you think I belabored it so much and making sure it worked right?

 mod1: const make object! [
      api: func [x] [print ["The value of x is" x]]
 ]

 mod2: make object! [
     mean-api: func [y] [
         print ["The value of y is" y]
         print ["And I'm going to mess with mod1"]
         mod1/api: void
     ]
  ]

The const on MOD1 make it a "view" of the object that cannot be modified unless MUTABLE is used to get at the data. But the internals of the module are not affected...only accesses through that mod1 variable:

>> mod1/api 1020
The value of x is 1020

>> mod2/mean-api 304
The value of y is 304
And I'm going to mess with mod1
** Access Error: CONST or iterative value (see MUTABLE): make object! [
    api: 'make action! [[x] [...]]
]

There's tools for most of what you could think of. e.g. the PROTECT here keeps the value of the variable mod1 itself from being changed.

>> protect 'mod1

>> mod1: void
** Access Error: variable mod1 locked by PROTECT (see UNPROTECT)

Everything is covered in one way or another, it's just about setting the policy and picking defaults. Whatever the syntax is for IMPORT as a keyword or something in the module header, it should give ways to specify what you want, and do a reasonable thing if you don't specify--whatever that is.

But again, you can use these features in your own code today. There are a lot of problems, but these are pretty far along on being solved.

1 Like