The brevity of DO is a strength that makes it seem natural to be polymorphic.
I thought it was very wasteful that when Rebol2 and Red's DO saw something it didn't recognize, it would just drop out:
rebol2>> do 1020
== 1020
rebol2>> do <useless>
== <useless>
Pinning people's expections to this behavior--to where they write code that relies on it--is awful.
There are plenty of things we can imagine for this undiscovered space. (Soon it's planned that the ReplPad will use shorthands like do 3 to mean "run the code in editor tab #3")
Could We (or Should We?) Take DO's Polymorphism Further?
I'd been thinking that it might be able to "run" things that are not even Rebol. This would mean that you could do %foo.js and run JavaScript, or do %bar.css and import CSS.
(But then again, JavaScript has modules vs. ordinary code... so maybe import %foo-module.js would also be an interesting polymoprhism to have as well?)
We are getting some experience with this and the ReplPad. And what experience is telling us is that it's really ugly to have to use names like JS-DO and CSS-DO. Plus it puts stress on coming up with and remembering the names (was it JS-DO or DO-JS?)
With FRAME!s we even have the option of building things that defer execution. Some simple pseudocode using current JS-DO to give the idea.
load: enclose :lib.load func [frame]
uparse try match [url! file!] frame.source then [
let result: make frame! :js-do
result.source: frame.source
return result
]
do frame
]
>> thing: load https://example.com/helloworld.js ; doesn't run JS yet
>> do thing ; would run it here
In today's Ren-C, the sky is the limit for many such things.
I Think DO of a TEXT! String Should Be Dropped (Reclaimed)
When you DO a BLOCK!, you know that block has been incarnated through some series of steps that bound it and brought it to life.
When you DO a TEXT! string, you have nothing to go on but the string itself. It represents an incomplete thought, and it's hard to think of a "good" answer for what the semantics of that should be.
One tricky issue we've talked about is how module headers in text strings are handled:
do "Rebol [Type: module] export thing: {This does not work}"
Historically what happens is that Rebol is a synonym for the SYSTEM object, so that evaluates an inert OBJECT! as a first step. Then it evaluates the inert block [Type: module]. You don't have any of the LOADer mechanics in there.
So basically, the above is completely broken.
We're facing another problem of being short on strings to say where code should come from. It's important to have a way to distinguish running paths relative to system.script.path
as opposed to WHAT-DIR, and strings seem a reasonable way to encode that intent:
do %../path/relative/to/what-dir
do "../path/relative/to/system.script.path"
import %../path/relative/to/what-dir
import "../path/relative/to/system.script.path"
When you add all this in with the spirit of language agnosticism, that makes interpreting TEXT! as being specifically Rebol language text is presumptuous.
This all makes going through do load to use text as source seem like a much more attractive option than trying to figure out how to push all of LOAD's options onto DO.
>> do "print {Hello World}"
Hello World
>> do load "print {Is DO LOAD that much worse for Hello World?}"
Is DO LOAD that much worse for Hello World?
Of course, I will make the usual point that you will be able to overload this if you wish. Redbol will still DO strings, and you can decide you're never going to use the script-relative path importing (or do it another way).
But I think DO LOAD is a small price to pay for solving a bunch of irritating problems.
I Think We Should Drop the /NEXT Option From DO
Clearly a DO/NEXT of a JavaScript file doesn't make sense. But does it make sense for a Rebol file, really?
Rebol2 returns a BLOCK! with a pair of the result and remaining code, which works for blocks:
rebol2>> foo: [print "Some" print "Block"]
rebol2>> do/next foo
Some
== [unset [print "Block"]]
But for functions, it just ignores the /NEXT:
rebol2>> foo: func [] [print "Some" print "Function"]
rebol2>> do/next :foo
Some
Function
Red gives nonsense, as usual...it returns the value of the function back and doesn't DO it at all
red>> foo: func [] [print "The usual" print "nonsense"]
red>> foo
The usual
nonsense
red>> do/next :foo 'pos
== func [][print "The usual" print "nonsense"]
red>> pos
*** Script Error: pos has no value
So I'm proposing the /NEXT functionality be solely in EVAL, and have EVAL run only on ANY-ARRAY!
If you DO, that means fire-and-forget.
Narrowing DO Use Is Good For Security / Avoiding Big Mistakes
DO is pretty powerful. When you say DO VALUE that could be a URL!...fetching arbitrary code off the internet and running it.
Of course, DO of a BLOCK! can contain code that does arbitrary things. But if you're writing code that's supposed to all run on your machine and be self contained, it would be nice if you could be reasonably sure that you aren't running code off the internet if you didn't use DO or IMPORT.
So making it possible to get normal casual work done locally without ever needing to call DO seems desirable. That is why EVAL has both "do to end" and "do step" semantics:
>> eval [1 + 1 print "Modes"]
Modes
>> [value pos]: eval [1 + 1 print "Modes"]
== 2
>> pos
== [print "Modes"]
So you can use EVAL where you would have used DO of a BLOCK!, and you can use the /NEXT mode as a multi-return (or a refinement, if you choose)
>> eval/next [1 + 1 print "Modes"] 'pos
== 2
>> pos
== [print "Modes"]
This raises into question if DO of a BLOCK! needs to be a way to run code at all. It could be dialected, and let you supply arguments:
do [%script-taking-args.reb 1 2 3]