Squik language features
ajh18 at cornell.edu
Wed Apr 16 16:58:50 UTC 2003
Sorry, I know my explanation was a little terse and loose. I'll be more
complete next time. Below are my response to your inquires. Thanks for
Jesse Welton <jwelton at pacific.mps.ohio-state.edu> wrote:
> Anthony Hannan wrote:
> > No VM
> > The boot program is not a VM; there is no VM. Any machine code needed is
> > implemented in library modules (dlls) that are loaded as objects when
> > needed. For example, Interpreter is an object that responds to messages
> > by calling the named function in its library module. These library
> > messages replace primitive syntax.
> > Interpreter is really the only object that requires machine code
> > implementation. All other traditional VM behavior like the garbage
> > collector can be implemented in the image. Raw pointer manipulation must
> > be added to the bytecode set, but only privleged classes will have
> > access to them (Low-Level Operations are described later).
> This strikes me as a misuse of terminology. If the bytecode set is
> not an instruction set for a virtual machine, what is it? (The
> architecture itself seems fine to me, but the terminology could use
> clarification. You shouldn't call a thing not-that-which-it-is.)
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
> > 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).
> > 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.
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. If several classes were grouped in the same
environment then each class would have access to the same set of
imports. It is unlikely that every class will need the same exact
imports. So if you wanted to be strict, which you do with capability
security, you would end up with a single envioronment for each class.
In light of this I opted to get rid of environments all together, and
make the class the environment. Java works this way, each class is its
own environment importing each interface/class that it uses. In Java,
the address of these imports is a class file pathname, in Smalltalk it
will be a direct pointer via a class variable.
> > 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.
> > 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 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. 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).
> > Namespaces and Bound Selectors
> > A class can only send messages of other class interfaces that are
> > visible to it. An interface of a class is the set of its selectors that
> > are new to the class and not inherited from any superclass. An interface
> > is visible to a class iff the class has the interface's factory in one
> > of its class variables or inherited class variables. For example, a
> > class A can send the message #do: to an object only if A has or inherits
> > a class variable that contains the Collection factory. For messages sent
> > to self, interfaces of its own class hierarchy are also visible.
> How is this applied to messages sent to the factories themselves
> (OrderedCollection new)?
Good observation. When you import a factory you automatically import
its instance class interface and its own class interface. So to use
#new you would import the Object factory that understands #new. This
would normally be imported early in the hierarchy like on ProtoObject,
so all subclasses get it automatically.
>From now on I will distinguish between imports and other class
variables. So a class now has a set of imports, which hold factories,
and a set of class variables, which hold any object. Methods can access
an import or class variable by name as if they were all class variables.
But the compiler only checks imports when looking up message sends.
> > 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.
> 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
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.
> > Multiple Inheritance
> > To support polymorphism of bound selectors, mulitple inheritance has to
> > be allowed. For example, if a subclass of Foo wants to simulate block
> > behavior it will have to inherit from BlockClosure as well.
> 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
> 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
> It seems to me a noninherited interface
> definition can provide namespaces for bound selectors without the
> added complexity of multiple inheritance, can work better with helper
> methods like asString, and can be more flexible in assisting security
> (as for example by providing restricted subsets of interfaces). Using
> inheritance for all scoping severely limits your options.
I understand where your coming from but like I observed for environments
and classes, behaviors and interfaces coincide so much that it makes it
much simpler to combine them. If your class is going to implement some
inteface it is likely that it will implement some common behavior.
So it is easier to just inherit from the behavior that defines that
interface and override the implementations you don't like.
With no instance variables and bound selectors multiple inheritance is no
longer complex. It is simpler than having separate interfaces and having
to reimplement common behavior (Java), or having mixins, etc.
More information about the Squeak-dev