2

Rebol has no keywords. It's a concept that forms a part of what makes the language so versatile—any word can be reassigned, any word can be assigned the value of a 'built-in' function—and one that in the general flow of the language holds true*. However there are notable exceptions within two of Rebol's basic structural datatypes that break that rule:

  • return within a function, and
  • self within an object.

There has been some discussion in the Rebol and Red chat room as to possible ways to build functions and objects where these words are not predetermined, my question alludes to my best understanding of that discussion: Is there an approach to removing the special qualities of these words that'd work while retaining the characteristics of each type? Take for instance:

document: make object! [
    title: none
    rename: function [title][self/title: title]
]

essay: make document []
essay/rename "My Document"

In the above example, we might wish to replace self with this-document:

document: object/self [
    title: none
    rename: function [title][this-document/title: title]
] 'this-document

How would this propagate to derivative objects without requiring a special word to keep tabs?

essay: make document [probe this-document]
essay/rename "My Document"

What would be the best way to express this word? The above, or something like:

document: object [self: 'this-document][
    title: "Untitled"
    rename: function [title][this-document/title: title]
]

Using a two-argument approach to creating objects opens the possibility that we could add more qualities to an object: type-checking certain fields, hidden or protected fields—but all of this would somehow have to be tracked within the base object structure as well.

* Dialects within Rebol—such as parse—do contain keywords, but the parse dialect is not processed by the Rebol evaluator, it's its own sub-language.

2

Is there an approach to removing the special qualities of these words that'd work while retaining the characteristics of each type?

Well, what are those characteristics? For SELF:

  1. Points to the object instance so it can self-reflect.
  2. Cannot be overwritten by your own code.
  3. Automatically present without you having to ask for it.
  4. Is not inherited as-is, but is updated to reflect a new object.
  5. Doesn't show up when you use words-of or mold.

Point (1) is technically possible, but due to point (2) you can't use the name self for a custom SELF (unless a decoupling happened and the word were opened up for user use). You can get it with binding:

document: make object! [
    x-self: bind? 'x-self
    name: "Document"
    rename: func [new-name] [
        x-self/name: new-name
    ]
]

>> document/rename "This changed name will print"
print document/name

A user-made "self" like x-self can get some of (2) with PROTECT, but today's SELF appears immune to UNPROTECT:

>> test make object! [unprotect 'self self: 10]
** Script error: cannot set/unset self - it is protected

User values can't do that, so if it were considered desirable there'd have to be something that made protects permanent.

For (3)'s automatic-ness, you can move the self-like thing into a generator. So going with the theory that SELF is no longer special and we call that generator OBJECT (as today's generator):

object: func [body [block!]] [
    make object! compose [
       self: bind? 'self
       (body)
    ]
]

How would this propagate to derivative objects without requiring a special word to keep tabs?

So this is (4). If someone doesn't use the OBJECT generator, what keeps the SELF convention going in inheritance steps? One answer is to just use another generator, e.g. an INHERIT that coordinates:

inherit: func [parent [object!] body [block!]] [
    make parent compose [
        self: bind? 'self
        (body)
    ]
]

A better idea would likely be to reuse the OBJECT generator if it had a way of acting like INHERIT based on another cue. Some keyword in the spec or body indicating the parent, so it could do the inheritance in that case.

Note: In order for these to work, something about SELF must allow it to be overwritten from the base class in spite of (2).

As for being able to hide the SELF in user-space in (5), the only way that can be achieved today is with PROTECT/HIDE. But HIDE as currently implemented doesn't just block it from view in enumerations, but also from new bindings. So that suggests another shade of meaning to protect to discern between whether something is not shown vs. not bindable.

This further raises the question about persistence across serializations. If you mold an object out, and SELF is invisible, when you LOAD it back it wouldn't have one. On the other hand, it were visible and it was a reference to an object, you're dealing with serializing object references in text. Because it's a reference to the object being serialized it could be done specially in mold syntax vs. an arbitrary object reference, but then it becomes special again.

TL;DR - In the case of SELF behavior being relegated to generators, serialization and deserialization is probably the hardest point--e.g. how to make it work where the generator is not in the picture. Yet it may be that needing a SELF at all is not so different from needing PARENT, where all the same problems would show up. So I wouldn't let that kill the idea, it's worth thinking about.

(As for RETURN...same basic issues, except people don't try to mold functions as a method of serialization...usually.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.