Quick tutorial on UnixObjects

This is a brief tutorial indended to quickly give a practical idea of UnixObjects. This article is aimed at ordinary users with a Unix background but not necessarily much experience with concepts associated to object-orientedness. Approximately 60% of the ideas in UnixObjects are touched upon here.


Table of Contents:

  1. Introduction
  2. The @ command
    1. 2.1  Execution
    2. 2.2  Dynamic inheritance
    3. 2.3  Prototypes and shared objects
    4. 2.4  File objects
    5. 2.5  Virtual objects
    6. 2.6  Assignment methods
  3. Summary

1.  Introduction

The goal with UO is to introduce a new organizational principle to Unix by adding as little as possible to the operating system. I call this principle organization by containment and I have been able to reduce the necessary additions to a single executable command.

In some cases you may not even need that one command. UO is mostly about organizing your files differently and thinking differently. This is why it is called an architecture rather than a system. And this is why software that is compliant with UO have names distinct from the name of the architecture (my software package is called Orgone).

While UO adds little to Unix in terms of new bits, the effect on productivity and stimulation of fresh ideas can be quite remarkable.

2.  The @ command

UO introduces a new command called @. This is the shell-level interface to objects of the Unix kind.

2.1 Execution

In its most basic use, @ executes the file given as argument. For example, the command line

    @ myprog a b c

executes myprog with a, b and c as its arguments, just like the shell would do on its own. After myprog exits, control returns to shell—@ is a one shooter, not a captive facility. If the file does not have execute permission, @ ‘executes’ it by writing its contents to standard output. Try

    @ /etc/motd

A key difference compared to how the shell works is that @ does not consult the PATH environment variable. Instead, @ behaves as if the variable was set “PATH=.”  So to execute a command in another directory we specify its pathname

    @ dir1/dir2/prog

Another difference is that before exec'ing prog in the above example, @ changes the current working directory to dir1/dir2.  Since this is a subprocess, the change does not propagate back to our shell session. But it provides an important reference to prog—for dir1/dir2 is its self.

Stated otherwise, prog is dir1/dir2's method.

Foregoing the PATH environment variable means that we are not following the Unix convention of collecting executables in a central “bin” directory. Instead, our files and directories are organized by containment. So for example, dir1/dir2 is not just a directory, it is a composite object that is expected to contain its own commands (methods) and data files (properties).

On a side note, because @ makes the executability of a file transparent to the user, the concepts “method” and “property” do not have the strong distinction between them in UO as they have in some other object-oriented programming environments. With UO, the choice of word reflects more on whether one is talking about evaluation (= method) or management (= property).

2.2 Dynamic inheritance

While our objects are logically self-contained and therefore do not depend on each other, there are situations where it would be desirable that a dependency existed. One example is the case of a derived object that introduces a small change to an existing object and apart from that retains the original behaviour. Collections of such derivations would quickly result in the need to replicate identical files all over the place.

UO addresses this problem by a dynamic inheritance mechanism. More specifically, an object can make use of the methods of another object by declaring the other object to be its parent.

    cd dir1/dir2
    ln -s ../other parent

The parent link can be changed any time (manually or programatically). With the link in place, sharing happens automatically. Any method not found in dir1/dir2 will be looked up from dir1/other. And likewise, any method in dir1/other that dir1/dir2 wants to override it just needs to create a local version of. Note that looking up a method from parent does not change dir1/dir2's association of self  (or current working directory).

The parent can have a parent link of its own, an so on.  @'s lookup algorithm will traverse the parent chain until the method is found or until the chain is exhausted. Any type of file, executable or non-executable, even a device special file, can be looked up this way.

With the introduction of the parent link, directories belong to two parallel hierarchies: the one inbuilt into the file system, which we call the containment hierarchy, and this one, known as the inheritance hierarchy.

2.3 Prototypes and shared objects

Sharing properties through a dynamic parent link allows potentially unlimited configurations and design patterns to be implemented. However, a generalization has been discovered that is known to work well in a wide variety of cases. In this convention, objects are categorized into three groups as follows.

Instances. Objects that perform active duty and are subject to change.

Shared objects. Objects that exist solely to serve as parents to other objects. Shared objects are not called directly. In some other systems, objects in this category are called “traits objects.”

Prototypes. Objects that exist solely to serve as starting points for instances. To create an instance you make a copy of a prototype and change it according to your needs. Prototype objects often have a preassigned parent link pointing to some shared object. Prototypes are typically chmod read only.

Instead of using a prototype as a starting point, it is of course always possible to create an instance from scratch and install any needed properties by other means.

2.4 File objects

Up to this point, we have talked about the directory as a composite object and regular files as its properties. However, a regular file can also act as an object, and @ can execute methods for it. Consider

    @ dir1/picture.jpg/width

Here, as in the previous example, the last path component is the method name (“width”), and the path components before it make up the reference to the object (“dir1/picture.jpg”). In this case, the object reference—also known as the object's id—resolves to a regular file.

Since a regular file can not contain a “parent link,” in UO, the immediate parent for each type of file is preassigned to a fixed location (configurable by the user). Apart from this difference, the lookup procedure is as described earlier.

A file object can also appear as an input stream. The id to denote standard input is the hyphen character (-). The above command line could have also been written as

    cat dir1/picture.jpg | @ -/width

even if in this particular case nothing would be gained by this complication.

Here is how one might implement the width method

    #!/bin/sh

    /org/imagemagick/bin/identify -format "%w" "$SELF"

The command identify is part of the freely available ImageMagick package.  “%w” specifies that we want the image's width (in number of pixels) to be reported.  SELF is an environment variable set by @.

Note, however, that this particular implementation is not specific to the jpg type (ImageMagick's identify command can report the width for a number of image file formats). So in practise, this shell script would belong in a more generic shared object, perhaps by the name of image, and the jpg specific shared object would then hold the symbolic link

    parent -> ../image

This allows us to take advantage of the generality of this implementation of width, and still have a place for other methods that are specific to JPEG images. Overall, the advantages of this arrangement are

  1. The user does not need to remember the command/syntax for obtaining the width of a specific type of image (“width” is highly intuitive).
  2. If a better way to discover the width of a specific type of image comes along, the implementation of the method can be changed without it affecting usage.
  3. Thanks to the polymorphic nature of UO's ids, the usage is exactly the same even for composite images stored as multiple files in a directory. So the user does not even need to care whether they are “eliciting the width” from a file or a directory.

2.5 Virtual objects

Earlier we saw how in UO the full pathname does not need to be resolvable by the standard Unix path resolution mechanism. As long as the id part resolves, the method name can be looked up by traversing the parent chain. But what if not even the id is fully resolvable? I.e. what if the unresolvable tail part of the pathname consists of more than just the method name?

In UO parlance, this scenario is called a reference to a virtual object. It is called “virtual” because the object being referred to by the id does not physically exist in the file system, and yet UO does implement a mechanism with which it can be made to appear to exist.

In the example below, the resolvable part of the pathname (known as the pathname's subject) is in black, and the unresolvable tail part (known as the pathname's predicate) is in red.

    @ /org/amazon/us/products/013937681X/price

The user does not need to be aware of this dualism. For all the user cares, this whole pathname might just as well resolve to a simple local text file having the contents “$32.76”. What happens behind the scenes, though, is that @ performs a method lookup for

    @ /org/amazon/us/dispatch

traversing the parent chain if needed. If an implementation for dispatch is found, it gets called with the following environment variables set

    SELF=/org/amazon/us/products/013937681X
    METHOD=price
    PREDICATE=products/013937681X/price

This gives the dispatcher the opportunity to represent /org/amazon/us/products/013937681X. (By the way, these same environment variables with their respective values are available to the method in all the earlier cases as well.)

In the above example, the dispatch method supposedly knows how to fetch book prices from Amazon.com over the Internet. But a dispatcher can perform any function desired, including:

  • decode container file formats (tar for example)
  • access different parts of the file system (perhaps with some filtering)
  • maintain a cache of results that are expensive to calculate
  • install software from vendor's site
  • make scattered data appear as a coherent whole (perhaps by combining data from local and remote sources)
  • mine data from Google
  • give a complicated system service an easier to use interface
  • decrypt and/or decompress files
  • synthesize data out of thin air

In the past, some of the functionality in the above list has been implemented in driver code as special file systems. But having the self instance available for storing data, and for on-the-fly persistent configuration of the “device”, has proven to be a more flexible and automatable solution.

2.6 Assignment methods

In the examples so far, our methods have provided read access to data. The opposite operation, i.e. storing values to objects is often just a matter of using the echo or cp commands in the shell. But not always. There may be issues with file locking, permissions, data validation or processing that necessitate the object to force all access to it to go through dedicated assignment methods. And, of course, in the case of a virtual object, an assignment method is the only option.

By convention, names of assignment methods end in the colon character (:)

    @ mydaemon/log/level: 6

Assignment methods operate within the bounds of self (or subject in the case of a virtual method), never storing data outside their own premises. A single assignment call may cause modifications to more than one property. By convention, assignment methods do not write anything to standard output.

3.  Summary

UO makes executability transparent to the user.

UO replaces the PATH environment variable with the parent link and introduces dynamic inheritance to Unix.

UO recommends the convention of categorizing objects into (1) shared objects, (2) prototypes, and (3) instances.

UO implements pathname polymorphism by allowing a pathname to refer to properties of directories, actions on files, and virtual objects.

UO introduces a simple and unified way of implementing methods for the above three kinds of objects. The only technical change to the traditional Unix execution environment is the presence of a couple of new environment variables. When desired, a command can therefore easily support both the traditional Unix style of execution and UO's concept of self.