What is LOAD, Anyway?

Ways To Turn A String Into A BLOCK! In Rebol2

There was TO BLOCK!, which would give you unbound code:

rebol2>> code: to block! "print {Hello} x: 10"
== [print "Hello" x: 10]

rebol2>> do code
** Script Error: print word has no context

And then there was LOAD, which would bind code into the user context, falling back on the lib context:

rebol2>> code: load "print {Hello} x: 10"
== [print "Hello" x: 10
]

rebol2>> do code
Hello
== 10

rebol2>> x
== 10

LOAD had an additional option of /NEXT, so you could go one item at a time:

rebol2>> load/next "print {Hello} x: 10"
== [print " {Hello} x: 10"]

The block you get back has the item scanned as the first element, and then the rest of the string as a remainder.

What you receive is bound into the user/lib contexts, so this actually works:

>> do load/next "print {Hello} x: 10"
{Hello} x: 10

Refactoring Things In Terms of TRANSCODE

A goal of R3-Alpha was to support modularization, and ambitiously to make the module system itself implemented as usermode code. So LOAD would not be native.

The code for scanning UTF-8 strings into unbound Rebol values one-at-a-time was done via TRANSCODE. But because R3-Alpha added unicode support, it had a quirk that it only took BINARY!, because Rebol strings could be encoded under the hood as 1, 2, or 3 bytes per character:

r3-alpha>> transcode/next to binary! "print {Hello} x: 10" 
== [print #{207B48656C6C6F7D20783A203130}]

You could also transcode things entirely in one go, but the result would still have an empty binary at the end:

r3-alpha>> transcode to binary! "print {Hello} x: 10"
== [print "Hello" x: 10 #{}]

Ren-C improved on this in several ways.

  • Uses "UTF-8 Everywhere", and is technically able to accept a string anywhere a UTF-8 binary would otherwise be required... if it makes sense to do so. You can transcode strings without making a copy as a UTF-8 binary first.

  • Multi-return values allowed the separation of the remainder into its own result. It is NULL when transcode is used without a /NEXT refinement (renamed to /ONE)

  • Antiforms not being legitimate block elements allow the distinction of "nothing scanned" as a main result from TRANSCODE/ONE

The results of TRANSCODE were unbound, leaving it up to higher-level functions to add binding if they wanted to.

So Now, What Is LOAD (and What Is SAVE)?

I don't really know.

It's clear you're supposed to give LOAD a file, and it gives you something back. But if the file contains a script, it seems to me that all load can give you back is some kind of MODULE! that the only thing you can do with it is DO it (once?) or IMPORT it (possibly many times?)... in which case, why did you need the middle man of LOAD?

Right now that's kind of where things stand in Ren-C... that LOAD is an implementation artifact of DO and IMPORT, used nowhere else.

R3-Alpha had an example where it could do something like this:

r3-alpha>> save/header %test.r "Hello World" [Title: "My Hello"]

r3-alpha>> read/string %test.r
== {REBOL [
    Title: "My Hello"
]
"Hello World"
}

r3-alpha>> load %test.r
== "Hello World"

If you try this with a BLOCK!, it will just be the block's contents... not the whole block:

r3-alpha>> save/header %test.r [a [b c] d] [Title: "My Block"]

r3-alpha>> read/string %test.r
== {REBOL [
    Title: "My Block"
]
a [b c] d
}

r3-alpha>> load %test.r
== [a [b c] d]

There's no enclosing block saved, and yet it's interpreted as the contents of a block. Hm, so what if the block had contained "Hello World"? Well, the block is of course lost in that case:

r3-alpha>> save/header %test.r ["Hello World"] [Title: "My Hello Block"]

r3-alpha>> load %test.r
== "Hello World"

We could arguably "fix" this using isotopes, e.g. SAVE could take a splice if you wanted the values to be spread in the script... otherwise it would preserve your value. But in order for me to make decisions like "is this reasonable?" I have to know what the use case is. And I don't know what it is. Are LOAD and SAVE some kind of generic value-to-file service? Or are they specifically for scripts?

Practical Matter: Bootstrap Rebmake Code

There's code in the bootstrap which says:

user-config: make object! load (join repo-dir %configs/default-config.r)

Then %default-config.r has lines in it like os-id: null. It expects to be able to resolve NULL to a definition, e.g. the one in LIB. But we don't want to bind into LIB directly (too easy to do dangerous modifications). So does LOAD always create a new context? Here we want an object, not a module... this is important to distinguish because the techniques used by modules don't scale. So if someone is going to LOAD thousands of objects, they shouldn't be modules... :-/