cloning/invocation

Marcel Weiher marcel at metaobject.com
Thu Aug 29 21:52:35 UTC 2002


On Wednesday, August 28, 2002, at 11:20  Uhr, Jecel Assumpcao Jr wrote:

> On Tuesday 27 August 2002 18:23, Marcel Weiher wrote:
>> Not if it can be hidden, just like you hide *not* making the copy.  I
>> wonde why you see one as changing semantics, and its inverse as not
>> changing the semantics.
>
> We are talking about an object with a mutable part (prototype for
> temporary variables and arguments) and an immutable part (bytecode,
> sources, literals, etc).

Er, no.  A *method* in Smalltalk and a C/Pascal function consists only 
of the parts you describe as "the immutable part".  Values for 
arguments are not defined inside the method, they are provided 
externally in a method context / stack-frame, which is not part of the 
method.  Both together are necessary for the invocation/execution to 
work.

Values for temporary variables are also not copies of the method, they 
are initialized by code running in the method.  Regarding any 
assignment statement that is ever executed in a method as "copying the 
method" is an 'interesting' point of view, but not one that I find 
particularly compelling.

>  Either making a full copy or a partial copy
> that shares the immutable part will give you results that behave in the
> exactly the same way.

You are calling something a copy that isn't a copy.  When I pass 
arguments into the function, I am not making a copy of anything that is 
inside the function.  Quite the contrary, I am providing information to 
the function that lives *outside* the function.

> Not making a copy at all will give you a different behavior. In
> particular:

No.

>>>  Without a copy, two
>>> different invocations of a method would share temporary variables
>>> and arguments.
>>
>> Oh, please!  Every C function, Pascal procedure/function, Smalltalk
>> method, etc. manages to do this without making a copy of the method,
>> simply by externalizing the mutable state.
>
> You have to properly initialize the stack frame.

Exactly.  You create a *new* stack frame and then initialize it with 
*new* values.

>  How do you do that? By
> copying some values from the code or some other area.
>
>           int foo ( char c ) {
>                 int count = 0; max = 99;
>
>                 while ( count < max ) { .... }
>           }
>
> Some initial code in "foo" must copy 0 and 99 to the right places on 
> the
> stack.

No, not even with your somewhat creative definition of "copying", which 
only applies to literals anyway.  To make this clear, consider the 
following example:

int foo( int arg1, int arg2  )
{
	return arg1 + arg2;
}

See, no copying of the function at all, but I still have invoked a 
function and passed it parameters.


>>>  With a copy, each one has a separate state.
>>
>> You don't need a copy for that, just a proper lookup algorithm.
>
> Two different invocations of "foo" will allocate different memory

Yes, allocate a *different* memory.

> addresses for "count" and "max", and each one will copy 0 and 99 to

Er, not really.  See above.

> those memory addresses. We can make them not copy by doing
>
>             static count = 0; max = 99;
>
> but now the two invocations will step on each other's toes.

That has nothing to do with it.

>>> Of course, the declarative "invoke with arguments" will almost
>>> certainly
>>> have implicitly in its semantics some form of copying.
>>
>> No, it is creating a new method context, and leaving the method
>> alone. Nothing is being copied.
>>
>>>  I am just  stating explicitly that this is what is happening.
>>
>> Well, then you are explicitly wrong about this ;-)
>
> I hope that the C example will make what I am trying to say clearer.

Yes, it did.  It made it clear that you are (a) using a somewhat 
creative point of view wrt 'copying' and (b) still wrong, even if I 
accepted that POV (which I don't).

>> Yes.  I am fully behind the importance of interactive object
>> construction, but I am convinced it doesn't go far enough.  In fact,
>> I want to make it even better than it is.
>
> So do I. In the message that started this thread I was proposing making
> changes to Self that I thought might make it better.

Yup.

>> With copying and modifications, you have to remember and be aware of
>> the history of an object to know what it is like now.  This makes the
>> whole thing more complex.  Once again, I believe there is a reason we
>> *write* method invocation (procedure calls) as:
>>
>> (1)	result := someObject  method: arg1 more:arg2.
>>
>> and not:
>>
>> (2)	method := MethodProtype copy.
>> 	method setArg1: arg1.
>> 	method setArg2: arg2.
>> 	method invoke.
>>
>> This seems 'obviously' more complex to me.
>
> I would like the system to have both. A base-level programmer would
> write (1) while a meta-level programmer could do something like (2).

I don't see any reason why they would want to do that, but that is not 
at all what I am talking about.

I am saying that I want something like (1) to be usable with objects as 
well as methods.

>> However, we do use object instance this way (2).  As far as I can
>> see, self says that (2) is really the unifying mechanism, but
>> provides both syntax and implementation-optimizations to use (1)
>> where
>> possible/necessary.
>
> You are right. Just like the exact same syntax
>
>                           obj x
>
> will return a non cloned object if "x" is a data slot but will cause 
> all
> the cloning we have been talking about if "x" is a method slot.
>  Two
> different semantics for the same syntax. It is an imperfect shortcut,
> but nobody seems to have a problem with it.

Well, I have a bit of a problem with a "unifying mechanism" that is 
used neither (a) by the user nor (b) by the implementation.  If there 
is no actual leverage, maybe the mechanism isn't actually useful?

>> My current approach is to say that (1) is the unifying mechanismmm
>> and potentially use (2) internally where necessary (though I'd rather
>> it were not necessary).  Objects, which can be constructed with all
>> the fun of self-style, can be turned into parametrized objects (which
>> have some class-like characteristics) by turning some of their
>> instance variables into parameters.  They are then incomplete, and
>> know that they're incomplete.  The user also knows that and exactly
>> how they are incomplete.  To be used, a parametrized object must be
>> provided with a context that provides the bindings for the
>> parameters.
>
> That sounds good.

Glad you like it :-)

>  In fact, if you allow lexical scoping you can actually
> get by without inheritance as in Beta or E.

Could very well be.  However, I am not in the "I have one construct 
that can do everything" business.  I call that "Turing-envy". ;-)   I 
am more interested in finding useful/usable mechanisms that might have 
a chance at tackling the "abstraction problem" I think we have in 
CompSci (in addition to some strange asymmetries in OO).

We can always refactor later ;-)

>  I am trying to convince a
> friend who is designing a language inspired on Tim Budd's Leda to go in
> this direction.

Interesting!  I am currently busier actually trying this construct ( 
constructed objects -> parametrization -> use of parametrized objects) 
in real-world situations, with existing languages.

>  My own proposal was in a different direction, but that
> doesn't mean I consider it better.

I was just following up on Alan's comments about "Exemplars".  Not sure 
what he has in mind there, but it does sound similar in some respects.

>>>  Compare this with the model
>>> presented in the Blue Book (which can also be seen in the
>>> Interpreter class in Squeak).
>>
>> Ahh, but this really is an implementation...
>
> Not necessarily, and that is one reason we are having such a hard time
> understanding each other.

Hmm...a byte-coded interpreter seems like a very specific 
implementation of the message-passing object system that is Smalltalk.  
But I do think that different levels are part of the problem..

>>>  You will probably tell me that the "normal"
>>> Smalltalker doesn't have to learn about stack pointers, method
>>> headers, context objects, etc. Then I will say that the normal
>>> Selfish programmer doesn't have to know that activation objects are
>>> obtained from cloning method objects!
>>
>> ...whereas you've said that cloning is the "model" you present, and
>> that the implementation doesn't actually do this.
>
> Exactly. There are levels upon levels.

But (see above) it seems to me that there is a level in the middle that 
isn't actually productive, because its model is neither used in the 
implementation nor presented to the user.  What's it doing there?

>  At the very top level we can
> think of a user typing expressions in a workspace and doing "print it".
> He can have a model like you described either in Smalltalk or in Self.

Sure.  Though I'd actually try to limit "typing of expressions" and 
doing "print it" as much as possible.  I'd much rather connect 
reasonably smart components, and resort to expressions (preferably just 
constraints) only when absolutely necessary.

[snip]

> And of course, this implementation might run on a processor that
> pretends to execute x86 instructions. And so on. Levels upon levels
> until we finally get to the turtles ;-)

Yes, you should never forget about the turtles ;-)

>> Anyway, this is all too low-level for me anyhow, and method
>> invocation is not really what I am talking about.  It is making
>> dealing with objects more like method-invocation (or function
>> evaluation, procedure call) rather than vice versa, making method
>> invocations more like munging objects.
>
> As I wrote above, I am big fan of languages which take this idea
> seriously even if I didn't adopt it myself.

Great!

Marcel




More information about the Squeak-dev mailing list