Objects, namespaces, dictionaries, and hashes
DRAFT IN PROGRESS
Prelude
One thing that was apparent whilst writing this proposal up: we're missing something in our declarations…
To create an empty (e.g., 0, 0.0, ””, or FAIL) variable of a given type:
I HAS A <variable> ITZ A <type>
To contrast with and as an adjunct to the usual:
I HAS A <variable> ITZ <value>
BUKKITs
BUKKITs are the container type. They may hold NUMBRs, NUMBARs, TROOFs, YARNs, functions, and other BUKKITS. Each entity within a BUKKIT may be indexed by a NUMBR or a YARN. These indices, whether NUMBRs or YARNs, referring to functions, variables, or other BUKKITs, are generically called “slots”.
Declaration
To create an empty object within the current object's scope:
I HAS A <object> ITZ A BUKKIT
To create an object based upon an existing object:
I HAS A <object> ITZ LIEK A <parent>
Behavior of this sort of inheritance will be described further below.
To define an object, its methods, and the contained variables all at once, the following block may be used within the HAI/KTHXBYE block (or another O HAI block).
O HAI IM <object> [IM LIEK <parent>] <code block> KTHX
One can also use that structure to create an independent object, not immediately bound to the current scope and callable from outside. This is preferred because it encourages code reuse. The same code is used outside the HAI/KTHXBYE block:
O HAI IM <object> [IM LIEK <parent>] <code block> KTHX
More details will follow, but I'm looking to integrate ideas from this thread from xrestaussuredx.
Old commands in the context of objects
The root object, the global scope we have dealt with so far, has been referred to as ”I” in the following language constructs:
I HAS A <variable> I HAS A <variable> ITZ <value> HOW DUZ I <function> …?
These forms of the commands effectively create variables and functions within the current object. Since we've been working only in the “root” object, that has always been where the variables and functions have been created. To declare and create variables within an object known in the current scope, I is generalized to an <object> name:
<object> HAS A <variable> HOW DUZ <object> <function> …?
To add a variable (indexed with a YARN) to an object that has been declared and is within scope:
<object> HAS A <variable> [ITZ <value> | ITZ A <type>] <object> HAS A <object> ITZ LIEK <parent>
To define a new function within an object currently within scope:
HOW DUZ <object> <function> [YR <param> …] <code block> IF U SAY SO
Slot access
To access slots of BUKKITs (from the scope that contains the BUKKIT), I propose doing so by naming the object and slot, separated by two exclamation points:
<object>!!<variable> <object>!!<function> <object>!!<index>
Additionally, runtime access (and array iteration) can be facilitated with some indirection. A question mark is substituted for the second exclamation point, meaning that the identifier that follows is a variable, and the value of the variable is then used as the identifier for the slot:
<object>!?<indirect>
For example:
I HAS A NINDEX ITZ "DISWON" I HAS A NOBJECT ITZ A BUKKIT NOBJECT HAS A DISWON ITZ "TREE" VISIBLE NOBJECT!?NINDEX BTW "TREE"
The unicode character interrobang (‽, u203D), and the “exclamation question mark” (⁉, u2049) are both legal substitutes for the pair of characters.
Note: I know this is possible with no punctuation at all. My gut is telling me that we want a bit of distinction here just as markers for the fact that we are pulling in objects and identifiers from a different scope. I'm willing to discuss it, though: this is not an issue that the whole proposal hinges upon.
Numerical slots (indexed with a NUMBR) do not need to be declared before being written to. They do not need to be contiguous: sparse arrays are possible. They may be heterogeneous: they can contain different types.
Inheritance of slots
Declaring a variable within the current object adds that variable to the object.
Accessing a variable from within the current object looks for that variable within the current object. If it is not found, it searches for the variable within the parent object, and on up the chain of parents until it reaches BUKKIT (the base object type), whereupon it fails.
Assigning a variable within the object first searches for it within the current object. If it has been declared within the current object, then it is set. If that fails, it attempts to access it within the parent object. Search continues in up the chain of parents. If the variable name is found up the inheritance chain, then that variable is declared and created within the current object (where the search started), and the value is set. If the variable search fails and the variable was never previously assigned, then it's a declaration error.
In this way, a child object has all of the values and methods of its ancestors, unless it replaces them within itself.
Scope
Functions do not automatically have full access to the containing object's variables. Write access may be gained to only the variables declared with MAH:
MAH <variable> [[AN] <variable> …]
Similarly, objects may declare read/write access to the next object up the containment hierarchy with MAH (but note that this is less useful in general, since with object orientation, we generally know a lot less about the containing object than the inherited type.)
A function has read access to its defined namespace's scope. It gains write access by declaring access with the MAH keyword. It has no (read or write) access to the calling scope.
When writing LOLCODE without objects, with only the primary HAI/KTHXBYE block, functions written in the main block had calling access to each other and to variables defined within that main block (subject to the normal read/write caveats with the MAH declaration). Note that this was because of the definition's scope and not the calling scope.
HAI
I HAS A LIFE ITZ "GUD"
HOW DUZ I QUOTE YR THINGY?
":":{THINGY}:""
IF U SAY SO
HOW DUZ I CHANGE?
MAH LIFE
LIFE R QUOTE LIFE
IF U SAY SO
VISIBLE LIFE BTW: GUD
CHANGE
VISIBLE LIFE BTW: "GUD"
KTHXBAI
Note that CHANGE can change a value within current scope, but only because it declared it with MAH. Functions could be read and executed within the current scope normally.
HAI I HAS A NUM ITZ 2 I HAS A TING ITZ A BUKKIT TING HAS A NUM ITZ 3 VISIBLE TING!!NUM BTW 3 VISIBLE NUM BTW 2 I HAS A NUTHERTING ITZ LIEK A THING VISIBLE NUTHERTING!!NUM BTW 3 NUTHERTING!!NUM R 5 VISIBLE NUTHERTING!!NUM BTW 5 TING HAS A BAR ITZ "BAZ" VISIBLE NUTHERTING!!BAR BTW "BAZ" KTHXBYE
In this example, both TING and NUTHERTING are contained within the root object. However, NUTHERTING inherits from TING and therefore implicitly starts out with (clones of) TING's values. Since TING's values are already notionally “within” NUTHERTING, there is no need to declare NUM within NUTHERTING before (re-)assigning it. NUTHERTING also gains values that are later added to TING.
(more to come on O HAI and creating/populating a BUKKIT wholesale and in place)
— Adam, 2007/07/13 22:20