Re: scope Ka-Ping Yee (ping@lfw.org)
Wed, 04 Nov 1998 03:07:45 -0800

[Fixing the brace style in Ping's response. --MarkM]

On Thu, 22 Oct 1998, Tyler Close wrote:
>
> If you have something like:
>
> to foo(arg1, arg2) {
> scope
> }
>
> It seems to me that the above code actually instantiates a new object of
> type foo with private data members arg1, arg2 and no methods.

That wasn't my understanding. You need to say "define" to define an object; then you use "to" to define the methods of the object.

Also, it looks to me like you intend 'scope' to evaluate to the new object. It actually returns a mapping of names to slots that corresponds to the scope -- that is, by returning 'scope', you are in fact exposing the 'arg1' and 'arg2' slots to the outside world.

> Therefore, if you had something like:
>
> to foo(arg1, arg2) {
> to bar(arg) {
> # bar this foo
> }
> scope
> }
>
> I think this would instantiate a new object of type foo with private data
> members arg1, arg2 and public method bar(arg).

As i had understood it up to this point, you would do what you've described here by writing

define foo {

        define arg1 := null
        define arg2 := null

        to bar(arg) {
            # bar this foo
        }

}

The outermost 'define' evaluates to your new object (whose behaviour is called 'foo'). That behaviour includes the private data members 'arg1' and 'arg2', and the public method 'bar(arg)'.

But this only makes you one object of this kind, and you could never make another one.

If, as i suspect, you had intended this bit of code to create you a new 'foo' using 'arg1' and 'arg2' as constructor arguments, you'd want:

define foo(arg1, arg2) {

        define self {
            to bar(arg) {
                # bar this foo
            }
        }

}

The outer 'define' creates a maker object with one method named 'run' which, when invoked, creates and returns an object that has 'arg1' and 'arg2' in its scope and a method 'bar(arg)'. Now that you have a maker you can call it as many times as you want to keep getting new instance objects.

> to foo(arg1, arg2) {
> to bar(arg) {
> # bar this foo
> }
> buffer = { 0 ... 8 }
> scope
> }
>
> I think this would instantiate a new object of type foo with private data
> members arg1, arg2, buffer and public method bar(arg); where arg1 and arg2
> are persistent members and buffer is transient.

Your line beginning with 'buffer' here would refer to a variable named 'buffer' in the nearest enclosing lexical scope, since it was not defined locally with a 'define'.

I'm not clear on what you mean by "transient". If you would intend 'buffer' to be used by 'bar(arg)' and remain the same between invocations of 'bar', then you would write that like this:

define foo {

define buffer := [0..8]

        to bar(arg) {
            # bar this foo
        }

}

If you instead want 'buffer' to go away between invocations of 'bar', you would move the 'define' line like this:

define foo {

        to bar(arg) {
            define buffer := [0..8]
            # bar this foo
        }

}

> Further again:
>
> to foo(arg1, arg2) {
> to bar(arg) {
> # bar this foo
> }
>
> to face() {
> to baz(arg) {
> # baz this foo
> ...
> bar(arg)
> ...
> }
> scope
> }
> buffer = { 0 ... 8 }
> face()
> }
>
> I think this would instantiate a new object of type face with private data
> members arg1, arg2, buffer and private method bar(arg) and public method
> baz(arg); where arg1 and arg2 are persistent members and buffer is
transient.

Pretty close, but it looks like you have the right general idea.

    define face(arg1, arg2) {          # this is function shorthand
        define self {                  # this is an object
            define buffer := [0..8]

            define bar(arg) {          # this is function shorthand
                # bar this foo
            }

            to baz(arg) {              # this is a method
                # baz this foo
                ...
                bar(arg)
                ...
            }
        }

}

You don't need to "throw the object over the wall" the way you seem to be doing with 'scope'. As long as the object definition is the last thing to be completely evaluated, it will get returned.

Basically you've got it, except that you're using 'to' for both objects and methods. In E as i understand it, 'to' is only used to define methods and 'define' is for objects.

Instead of using 'define' for objects, i now see that you're using 'to' for scoping and making functions, and then expecting 'scope' to make you an object out of the current scope by turning functions on only the current level of scope into methods on the object. It's an interesting way of looking at it -- i think in fact it is isomorphic.

For the sake of a consistent understanding, i will describe the translation. Tyler-E (if i may call it that) removes one level of braces from E with a simple substitution that looks something like:

    E                      <===>   Tyler-E


    define self {                  <behaviour>
        <behaviour>        <===>   scope   # better named, e.g. "make"
}
    define <name>(<args>) {        to <name>(<args>) {
        <expressions>      <===>       <expressions>
    }                              }


(It doesn't matter where the data members are declared in the object definition.)

!ping