Squik language features

Jesse Welton jwelton at pacific.mps.ohio-state.edu
Wed Apr 16 21:02:14 UTC 2003


Anthony Hannan wrote:
> 
> Jesse Welton <jwelton at pacific.mps.ohio-state.edu> wrote:
> > Anthony Hannan wrote:
> > > 
> > > No VM
> > > [...]
> > 
> > This strikes me as a misuse of terminology.  [...]
> 
> I am using the VM term broadly to include all the none image parts of
> Squeak.  The interpreter is one part of the VM which does have its own
> instruction set.

In that case, I'm confused as to why you've said this will have no VM.

> > > No Instance Variables
> > > 
> > > There are no instance variables. Instance fields can only be accessed
> > > via #instVarAt: and #instVarAt:put:. Of course, accessor methods may be
> > > implemented for convenience.
> > 
> > Again, you don't really not have instance variables; you've just
> > removed their labels.  This seems like a bad idea to me.  Even if you
> > remove direct (bytecoded) access to them, you'll still want their
> > names available for introspection, such as in the debugger.
> 
> The purpose of removing instance variable names and replacing them with
> accessors is so subclasses can override them.  Automatic inheritance of
> state is too binding to the implementation.  Getting rid of instance
> variables names means you only inherit behavior.  The number of fields
> in a subclass is under control of the subclass and is not automatically
> inherited from the superclass(es).

Okay, this is actually kind of interesting.  I still think you'll have
to consider how the field information is displayed in introspective
tools like the debugger.  The names are far too valuable to simply
give up.  (Also, now I don't understand what you were talking about
when you described the compiler automatically recompiling accessors
when superclasses' ivars are moved about due to MI.)

> > > No Global or Pool Variables
> > 
> > Having scoped environments in place of global and pool dictionaries
> > seems like a good generalization, but I'm skeptical about tying this
> > rigidly to the class heirarchy.  One shouldn't need to change the
> > inheritance structure of a class in order to specify what package
> > facilities are available to it.  These are independent concepts.
> 
> You can think of environments being independent of classes, but you can
> also think of them as one in the same: one environment per class.  This
> provides finer granularity of imports and reduces management complexity
> of maintain and environment hierarchy and a class hierarchy that is
> intertangled with each other.

I'm not sure that using inheritance to determine environments does in
fact reduce management complexity.  It certainly doesn't provide finer
granularity in principle, since uncoupled environments could be
defined in arbitrarily fine heirarchies independent of the class
heirarchy.

> Finer granularity of imports means a class can only use interfaces it
> directly imports (via its class variables and inherited class
> variables).  This provides finer control over security: you can only use
> what you import.  [...]

But by linking this to inheritance, you're limiting your flexibility
to provide security, in that all descendants of a given class always
have access to that class's imports.

> > > Implicit Temporary Variable Declaration
> > 
> > What about lexically nested contexts?  You need a way to determine the
> > scope of each temp var.
> 
> The compiler can figure this out automatically, and usually does a
> better job than the lazy programmer that will declare all temps at the
> top level even if some are only used in a block.

  testAutoScoping
    setter := [:val | var := val ].
    getter := [ var ].
    ^Array with: setter with: getter

What's the scope of var, and how does the copiler know?

  testAutoScoping2
    [ 1 to: 1000 do: [:i | doSomething: i] ] fork.
    1 to: 100 do: [:i | doSomethingElse: i].

Any interference here?  By the time you've refined your scoping rules
to the point that the compiler can come up with the right answer to
all reasonable cases you've anticipated, will they really be easy
enough for programmers to anticipate the behavior of a given case at a
glance?  And are you sure you want to completely disallow shadowing?

With variable declarations, their scope is immediately clear at a
glance, because the rules are dirt simple.  It also guards against
accidental shadowing and typos.  And of course, the compiler can
detect undeclared variables and (with the programmer's consent) insert
the declaration in (usually) the right place according to heuristic
rules (which the programmer can check, and change if necessary).

> > > Factories
> > 
> > It's not clear to me why this should be considered an improvement over
> > metaclasses.  You've just taken two objects which need to be
> > maintained in parallel heriarchies (Object, Object class) and replaced
> > them with three (Object, ObjectFactory, Object instanceClass).  This
> > seems especially odd given the association you propose between a
> > factory and the interface defined by its instanceClass, below:
> 
> You still only have two parallel hierarchies the instance class
> hierarchy and the factory hierarchy, there is no longer a metaclass
> hierarchy.

The way you described it, there is a class heirarchy of instance
classes, and a heirarchy of factory classes, plus the factory objects
themselves (instances of the factory classes), one per class.  While
the factory objects do not have inheritance ties to one another, they
still exist in a sort of parallel heirarchy.

>  The separation of a factory from its class is that it allows
> the factory to instantiate any class its sees fit, instead of the one it
> is associated with.  This could be done with metaclasses as well but
> would seem ironic that a class creates instances that are not its own
> class.

If you have an architecture in which the factories are not tightly
associated with particular instance classes, I might agree that this
is somewhat more natural.  But you've described instead a system in
which access to the protocol of the instance class is determined by
having the *factory* in a class's (possibly inherited) class
variables.  That means you're tightly associating a particular
instance class with a given factory.

>  Furthermore, factories provide a protected interface to its
> instance class so users that only want to create instances don't also
> have the capability to change methods (again capability security).

Isn't that more naturally controled by granting or witholding
capabilities via the compiler interface?

> > > Selectors are more than just symbols. A Selector points back to the
> > > interface that it is a part of. The compiler binds message sends to the
> > > selector found in a visible interface. If more than one interface
> > > contains the same selector name than the compiler pops up the choice to
> > > the programmer. The programmer usually knows which interface he is
> > > targeting. The interface chosen is prefixed to the message, such as
> > > "block blockClosure.value".
> > 
> > This may be a good way to handle selector collisions between
> > protocols.  But it may not: it's verbose, and could produce alot of
> > false positives.  Consider the implementation of Dictionary, which
> > uses both BlockClosures and Associations.  Even though there's no
> > internal conflict in interpreting #value sent to either a block or an
> > association, Dictionary code would have to specify which protocol
> > applied in each case, simply because it has access to both.  (That is,
> > unless you also propose to add static type checking.)
> 
> In this case you would separate the #value protocol out into a new
> superclass that both BlockClosure and Association would inherit from
> (remember multiple inheritance is allowed).  The senders of #value would
> then bind to this new superclass protocol.  This makes explicit the
> shared protocol that would otherwise be implicit and subtle.

The point is that this is not a shared protocol.  The #value method
means something very different in each case.  Even if you want to
argue that #value does belong in the same interface in this particular
case, the example is still structurally correct: There must exist
identical selectors in logically separate protocols, otherwise you
don't need the disambiguation system you described.  These will cause
headaches for clients which need to talk to objects speaking one or
the other (but not necessarily both).

> > To what interface does a method like #asString belong?  If to Object,
> > you're losing much of the encapsulation you're working for.  If to
> > String, you've got an unworkable problem with interface through
> > inheritance.
> 
> Like #value, you can move it out to a shared superclass.  But I don't
> think it is a problem leaving it on Object if you believe all object
> should understand it.

You miss the point, which is that all objects should understand it,
but possibly not all objects should have access to it.  Let me think
what would make a better example...  How about #storeDataOn:, used by
tools which serialize objects to disk, but likely something you want
to restrict access to outside that context.

> > > Multiple Inheritance
> > 
> > This seems like another overuse of inheritance to me.  In order for
> > MyBlockClosureReplacement to *simulate* a BlockClosure, it has to
> > inherit the implementation (and state!) of BlockClosure?
> 
> You don't inherit state because there are no instance variables.  Any
> method implementations that you inherit that you don't like you can
> override.
> 
> >  That sounds annoying to work around.
> 
> I don't think so.  It is likely that you will want to inherit most of
> the method implementations.  You will mostly be concerned with
> overriding accessors.

This does make more sense when state is not inherited.  Hmm, it does
require exposing a class's implemenation details (including access to
its dependencies) to any replacement class, though.

-Jesse



More information about the Squeak-dev mailing list