PLE treasure hunt -- Self


The OOPSLA'87 paper claims that
structural simplification makes it easier to understand the language
but also
As the variety of constructs decreases, so does the variety of linguistic cues to a system's structure.
Is the syntax in Self too uniform? What about LISP? Java? Python with tables instead of classes? Is experience with the language a factor?

Compiler vs. interpreter

Self has a dynamic compiler which optimizes frequently-used code while it's running. Does this make Self a compiled or interpreted language? Given this technology, is there any reason for a language to be strictly compiled?

Many languages, including Self and Python, parse code into compact instructions for a virtual machine. In some sense this is compiling, in another sense interpreting. Is the distinction relevant anymore?

Static vs. dynamic scoping

Is Self statically or dynamically scoped? Suppose we define an object containing a constant and a method to retrieve the constant:
| p = (| x = 5. f = (x) |) |
From an environment where x is bound to 4, a call to f could either:
  1. return 5 (looks like static scoping)
  2. return 4 (looks like dynamic scoping)
  3. return a number different from 4 or 5 (yikes!)
Here's how:
  1. (p _AddSlots: (| g = (| x = 4 | f) |)) g
  2. (| x = 4. parent* = p |) f
  3. (| x = 3. parent* = p. g = (| x = 4 | f) |) g
Here is the same example in C++. What is the ambiguity here? How did Python avoid this ambiguity? (Hint: how is procedure call implemented in Self?)

User-defined control structures

Virtually all control structures in Self are defined in the language. Only the loop message, which evaluates a block endlessly, requires low-level support. For example, this is the definition of untilTrue:, Self's version of Pascal's repeat ... until statement:
untilTrue: cond = ([ self value. cond value ifTrue: [ ^nil ]] loop)
In English:
  1. The receiver of this message (self) is the body of the loop. So the first thing we do is evaluate it for side-effect.
  2. Then the condition block cond is evaluated. If the answer is true, we return nil from untilTrue:. Caret is the return operator in Self.
  3. Otherwise, we go back to step 1. To do this, we enclose the first two steps in a block and evaluate it endlessly.

Your task is to implement C's for loop in Self. This construct has four parts:

  1. An initializer expression which is evaluated exactly once, before the loop starts.
  2. A condition which, if false, exits the loop. It is evaluated before the body.
  3. A body.
  4. A post-expression which is evaluated after the body, on every iteration.
Your solution should be a method while:Do:Then: which takes these arguments in order. The implementation should look similar to untilTrue:. Use this to test:
| i |
[ i: 0 ] while: [ i < 10 ] Do: [ i printLine ] Then: [ i: i + 1 ]
In C, this corresponds to:
int i;
for( i = 0 ; i < 10 ; i++ ) printf("%d\n", i);
If you can't work out the syntax, explain in English.

Closure cleverness

Suppose we have a function f which uses a local variable:
| f = (| x = 3 | (self + x) * x) |
4 f                "prints 21"
Suppose we're debugging, and we'd like to know about the run-time behavior of f, e.g. how many times x is used during execution. In this simple case, we know it is used twice; but f could be more complicated. Furthermore, we want to treat f as a black box; no modification of code.

Describe, in English, how we might write a function monitor: which modifies a black-box function so that all accesses to a particular variable cause a message to be printed. The monitored function should otherwise behave normally. For example:

((lobby _Mirror at: 'f') contents) monitor: 'x'
4 f                "prints 'reading x','reading x', and 21"

Barometer question: What are the features of Self that allow us to do this?

PLE Home page
Last modified: Fri Oct 4 18:17:28 EDT 1996