Naming and Referring to Objects

William Kent
Database Technology Department
Hewlett-Packard Laboratories
Palo Alto, California

April 1990



> 1 INTRODUCTION . . . 1
> 2 DESIGNATING OPERATORS AND OPERANDS . . . 2
>> 2.1 Icons . . . 2
>>> 2.1.1 Operands . . . 2
>>> 2.1.2 Operators . . . 2
>> 2.2 Naming Conventions . . . 3
>> 2.3 Variables . . . 3
>> 2.4 Designation By Function Composition . . . 3
> 3 DESIGNATING RESULTS . . . 4
> 4 PRIMARY NAMING CONVENTION . . . 4
> 5 CONCLUSIONS . . . 6


1 INTRODUCTION

The paper [obrefidx] dealt with unique handles for objects, which are usually system-generated and not intended for direct external use. This paper explores means by which users actually refer to objects in order to perform operations. In effect, we explore various mappings between external icons and internal handles.

This is largely tedious detail that we need to get out of the way in order to escape later ambiguities and confusion.

2 DESIGNATING OPERATORS AND OPERANDS

2.1 Icons

2.1.1 Operands

In graphic user interfaces, the operand of an operation may be designated by a user action selecting some sort of iconic image, which we can show as #. That icon is itself an object, but most often the user is intending to operate on some other object designated by that icon. (As described in [obrefidx], such a graphic icon is an example of a token which is both outside and inside the computational system, i.e., at its interface.) We can imagine a mapping

Designee: Icon -> Object

which identifies the object designated by an icon. To be precise, in terms of [obrefidx], we would say that this mapping yielded a reference to an object, i.e., an occurrence of its handle.

As an example, Designee(#) might in this case be (a reference to) a certain memo.

A user sometimes moves an icon because, say, he wants to move a memo to another folder, and sometimes because he wants to rearrange the desktop on the screen. The user interface management system (UIMS) provides some conventions to distinguish the two actions, effectively translating them to

MoveMemo(Designee(#)), MoveIcon(#).

The first is an operation on the memo, while the second is an operation on the icon itself. (For simplicity, we've left out the second operand of MoveMemo, i.e., the target folder.)

The MoveMemo, MoveIcon, and Designee operators are generally hidden inside the UIMS. Other hidden operations define the value of Designee(#), establishing the connection between the icon # and a memo.

The visual token on the screen might be more elaborate, such as the display of a spreadsheet or a graph. These visual tokens are still distinct objects from the objects they denote, namely the abstractions of the spreadsheet or graph.

2.1.2 Operators

Operators themselves are modeled as objects, having handles of their own.

When the user wanted to move the memo to another folder, he performed certain actions such as clicking and dragging, which we can here symbolize with the mark ***. The UIMS translated *** into the MoveMemo operator object, which in effect is the value of Designee(***). The string "MoveMemo" might never have actually arisen in this scenario. In effect, the requested operation can be described as

Designee(***)(Designee(#)),

i.e., the operator which is the Designee of the actions *** is to be applied to the object which is the Designee of the icon #. Translating such actions and icons into such requests is the business of the UIMS. The UIMS might do this indirectly, cascading through naming conventions described below, in which case the string "MoveMemo" might in fact be involved.

Note that some operations, such as MoveMemo(Designee(#)), are passed by the UIMS to other facilities for execution. Other operations, such as rearranging the desktop via MoveIcon(#), might be executed by the UIMS itself.

2.2 Naming Conventions

In lexical syntaxes, such as provided by command interfaces, operator and operand objects are generally designated by name strings.

A name string is, in effect, a degenerate form of icon, and plays much the same role. One of the things an expression parser does is to detect name strings, which thereafter serve as icons.

An expression written foo(bar) is understood to signify an operator named "foo" applied to an operand named "bar":

Designee("foo")(Designee("bar")).

To be ultimately precise, we'd have to do that for the operator named "Designee", which gets us into infinite regression, which is why we assume the common conventions under which such things are taken for granted.

Details of naming conventions are explored later.

2.3 Variables

In linear syntax interfaces, the operand may be designated by a variable, as in f(x). The operand so designated is determined by evaluating the variable at run time, after the variable has been bound.

We could treat variables themselves as ordinary objects, so that an expression containing a variable x is implicitly referring to Eval(x). In a sense, a variable is a degenerate form of expression. Thus f(x) is effectively equivalent to f(Eval(x)).

To be more precise, we might say that the variable itself is an object designated by naming convention, so that f(x) is really equivalent to

f(Eval(Designee("x")),

i.e., apply f to the result of evaluating the variable denoted by the name "x".

2.4 Designation By Function Composition

In the expression f(g(x)), the operand of f is being designated as whatever the value of g(x) is. A common example arises with explicit use of names, in a form such as

f(TypeNamed("Document"))

Such composition (nesting) is the basis of forming expressions. The capability is supported to varying degrees in various interfaces and languages.

If we think of variables as degenerate expressions, then designation by variable is a special case of designation by composition.

3 DESIGNATING RESULTS

By and large, operator results go to one of two places: as operands to other operators in expressions (including update operations), or across a user interface as "output".

In the first case, result designation is nobody's business, being the internal representation of objects hidden inside the implementation.

In the second case, it turns out that not all objects are suitable for output across a user interface (lexical or graphic). The results of many operations are often implicitly converted by the UIMS into such objects as strings or images, effectively applying some inverse of the Designee operator. [Relate to PNames introduced below.]

Note that this even applies to elementary operations on literals, such as arithmetic. The results are converted from abstract numbers (with internal representations hidden in the implementation) to displayable character strings.

4 PRIMARY NAMING CONVENTION

We can postulate that certain objects have special names, herein called "PNames". ("P" might stand for primary, or principal, or print, or proper; which do you like?)

The general idea is that a PName can be used in an expression in place of the object it names. We use that convention all the time for operators; we take it for granted that the corresponding operator object will be plugged in wherever we write its name. The names we give to functions (operators) when we create them are PNames.

(Overloading needs a separate discussion. We treat that as a mapping from indirect operators to direct operators, rather than a mapping from names to operators.)

PNames can also be used on output, displaying the PNames of objects instead of their oid's whenever that makes sense.

It's useful to design PNames into the semantics of the core model. Otherwise, we would require external parsers to convert all expressions into function and argument objects. That's not always possible when variables and nested or overloaded expressions occur. It also can be very inefficient in a serverized environment, since the language processor in the client may have to call the object manager in the host several times in order to identify the objects being named. Furthermore, there may be good reason for the object manager itself to be able to process expressions in external forms.

PNames are only defined on certain types of objects, and we could consider various scopes of uniqueness. The simplest design is to make them globally unique, and postulate a single "PNamedObject" type of which other things could be subtypes.

However, global uniqueness is probably not a practical constraint. PNames generally occur in contexts where a particular type of object is expected, hence PNames only need to be unique within such types. For example, if parsing isolates the function (operator) portion of an expression, then a name string occurring there only needs to be unique among function names.

For non-overloaded functions, the argument types are known, and PNames only need to be unique within the expected types. This is the case with most system operations. In the Create operation, for example, the first argument must be a type. PNames can be resolved here so long as they are unique within the type Type. When it is types that are being created, the names given to them are PNames.

PNames are mainly of concern with non-literal (surrogate) objects. If we wish, we could think of the representations of literals as being their PNames.

We can explore ways to extend the PName facility to user-type objects. Certain constraints might be useful. Only certain types are PNamed, and it might be desirable for them to be disjoint. That is, we might not want an object to have different PNames under different types.

The following is some speculative detail...

In the simple case, foo(bar) should mean the function with PName "foo" applied to the object of type T with PName "bar", where T is the type on which foo is defined. (This proposal extends naturally to functions of multiple arguments, in which case T is a list of types, one for each argument.)

But...

When foo is itself an expression, the result must be a function. It might be an indirect (overloaded) function, requiring resolution. Such resolution requires late binding, awaiting the evaluation of the expression. We could also allow the expression to be string-valued, and iterate this whole algorithm, but I don't think that's useful.

When foo names a variable, it must be of type Function. Again, any overloading would require late binding. Runtime errors could arise if foo names an untyped interface variable.

Otherwise, foo must be a function object or the PName of one.

In all these cases, the resulting function foo might be an indirect function, i.e., overloaded. Then the type T on which foo is defined is not known.

When foo is not overloaded, so that the type T is known, then:

When foo is overloaded, then: