Language Evolution
June 3, 2005, 9:40:49 am

I thought I'd share some programming language thoughts with you. Ignoring the sound advice that you should never build a language unless you have a problem to solve with it, I'm going to explain some of my thoughts behind what I'd want a new "Next generation after Smalltalk" language to do.

I firmly believe in "less is more", so long as it's the right kind of less. There are lots of languages out there that declare types for instance variables. Smalltalk doesn't - but it does make you declare instance variables. This, I think, is a bad thing. It means that the shape of a class must be "overridden" if you want to "extend" it to do more jobs. So my first change to Smalltalk would be to remove instance variable declaration.

I know what you're thinking/saying - you can't do that, you'll make typo's and make two instance variables slightly named differently. There are a couple of answers to this: (a) write unit tests and (b) use an environment that will helpfully suggest that perhaps you have misspelt something that already exists.

Moving on from there, we realise we still need to declare local variables - otherwise they become instance variables which wouldn't be so good. So I'll leave that alone for now.

Next on my agenda is message sends and variable accesses. I like what Self did - it unified variable access and message sends in to one single concept - message sends. You can do this by stating that methods are variables filled in with a compiledmethod. When you 'access' the variable you are actually running the code it contains. This is a very Self-ish sort of scenario, where you have slots that you fill with 'stuff'.

If we can put methods in any ol' slot, we don't need classes any more. We can stick to just plain objects. But you'll need some way to do inheritance. Self did this by having slots that start with a * act as inheriting slots. This isn't a bad approach and it allows multiple inheritance. The problem with MI is that it introduces ambiguity. So let's borrow a page from the book of DoesNotUnderstand and add a new exception called AmbiguiseMessageSend. This allows us to intercept it if we need to.

An ambiguous send happens when non-shared roots of multiple inheriting paths offer different compiledmethod's for the same lookup. That's a mouthful and I won't try to explain it further at this point. Suffice-th to say that you can always know if a call will be ambiguous or not.

So any message send could call some new code or return some value stored in a slot. This means we no longer have to write "getter" methods for our code. Next we need a way to do "setters". I'm going to invent a new syntax here and say that := can be used on the end of a message send to mean "assign to that slot". Eg: person name:= 'bob'. This way, we don't ever have to write "setters" either. Obviously the at: and at:put: reflection protocols will let us get around setting and getting if we need too (which we would, otherwise how would we get our compiledmethod back out of an object!)

So how does the sort of code look? Let's try some examples:

Smalltalk code:
buildFeed
    | fQuery |
 	self query value isEmpty
 		ifTrue: [^Dialog warn: (UserMessage defaultString: 'No Query specified!' key: #bfNoQuery)].
 	fQuery := self getBlogPulseQuery.
 	Cursor wait showWhile: [self feedViewer
 			addFeedsOrFeedFrom: fQuery].
 	self feedViewer checkNewAndAlertStates.
 	self accept value: true
 
New language code:
buildFeed
	| fQuery |
	query value isEmpty
		ifTrue: [^Dialog warn: (UserMessage defaultString: 'No Query specified!' key: #bfNoQuery)].
	fQuery := getBlogPulseQuery.
	Cursor wait showWhile: [feedViewer addFeedsOrFeedFrom: fQuery].
	feedViewer checkNewAndAlertStates.
	accept value: true 
 

A lot of the 'self' receivers disappear because we can talk about ourself more fluently now. This makes it very clear when we're talking about something not related to us.

Some unique things that happen when you start to break the language down like this. An instance of an object isn't sacred to the class that created it. It inherits it's behaviour from "A place" which is just a value in a slot - you can change this whenever you want. You can turn a Person in to an Organisation without any intense cost. Or for a more realistic example, you can change the security policy set on an object - which is inheriting security behaviour - by giving it a different security object in its inheriting security slot.

I'd say this is the start of what I'm looking for in a language - I have a lot more to say on the matter, so I hope to write some more posts on this new mythical language when I get the time.

By Sean on June 3, 2005, 11:22:03 am

Didn't smallscript do this with inst var and method namespacing?

IIRC, I attended a tutorial by Dave Simmons at the StS in Toronto where this was touted as a language feature (for the same reason as you want it). In fact, declaring the same inst var twice (same name) wouldn't be an issue because they would be in different packages and the namespacing would make references unambiguous.

I was certainly impressed at the time.

By Rich Demers on June 3, 2005, 12:14:04 pm

Comment by Rich Demers

Michael,

I like your idea of eliminating the declaration of instance variables. Years back, I did quite a bit of programming in IBM's REXX (procedural scripting) language, which does not require variable declarations; so a language can be designed that way. As a programmer, I found this a convenience, and it never caused problems that were not easily found. In thinking through the implications of this to compilers and tools, I was not able to find any show stopper. Once a variable has been implicitly declared, the compiler knows about it and can use that information in the compilation of future methods, and in the automatic reshaping of instances.

I am a lot less thrilled with the idea of eliminating classes. I know that Self does not have them (I'll confess a complete lack of experience with Self.), but I believe classes serve an important role in the programming process. Through Smalltalk's great tools, they give programmers a clear view of what constitutes the "programming" of a system. In fact, I would prefer to see a lot more use of classes; for example, events should have been described by subclasses of Event (just as exceptions are subclasses of Exception) -- and so should things like namespaces, processes, threads, protocols, packages, and bundles. In my opinion, all such conceptual entities in the base system should be described by subclasses. I believe a big mistake was made in not doing so.

Smalltalk programs, in general, suffer from a lack of adequate documentation. When you are "hot in the code" it always seems a waste of time to explain what you're doing, but pick up the same code a few months later (or pick up someone else's code), and your only recourse is "code forensics." This is (again in my opinion) an area in which the Smalltalk community is self defeating.

Your blog posting was about the "Next generation after Smalltalk" so you can advocate whatever you want, but I remain concerned about the evolution of Smalltalk itself. I've outlined my views on Smalltalk evolution several times in my blog, so I won't repeat them here.

By Brian Rice on June 3, 2005, 2:22:46 pm

I think this is ill-conceived and ill-informed. Self's inheritance has a linearizing effect, so that "ambiguous sends" are not meaningful (they are meaningful in Cecil for other reasons, but this is irrelevant to your case).
Also, inheritance via delegation slots is very problematic and breaks encapsulation and abstraction levels. Having used it as a basis for Slate, which has /far/ more developed usage patterns and libraries than Self, I can tell you that they suck big-time. Right now we have an emulation of Smalltalk-style Traits semantics and a meta-level distinguishment to prevent some very serious bugs that the Self team just plain ignored. If you use Self long enough, you just get burned, plain and simple (and only a few in the world ever really used Self enough to realize its limitations).
One other issue is that you are both (1) proposing to eliminate special syntax support for direct instance variable access (I agree - Slate does this, too) and (2) wanting slot addition to be implicit. The problem here is that you are basically saying that every mis-understood message will result in a new slot. It's one thing to say that unit tests can help you cover this case - it's another when unit tests /themselves/ get mangled state because of it, making your tests themselves suspect. Also, when/where do you provide lookup overrides (like what doesNotUnderstand: does), and how is that controlled? Such a mechanism would have to be the basis of what you were doing, or be inconsistent.
Also, keep in mind that your new idea for := being a syntactic convention is similar in principle to what a macro does. In fact, this is very easily a Slate macro, which we have considered. The problem is that without an explicit syntax extension framework, := could get a bit tricky, grammatically.
I have more to say, but it won't fit into this little comment box - see http://slate.tunes.org/ where the results of years of work has been used to build up a real language with a real set of libraries that can do more than even you imagine.

By Michael Lucas-Smith on June 3, 2005, 7:53:18 pm

Comment by Michael Lucas-Smith

That sounds interesting Sean. I'll look it up.

By Michael Lucas-Smith on June 3, 2005, 11:44:44 pm

Hi Rich. You're right classes and other important structures should take a prominent role in a language which deals with objects. You've actually jumped me ahead further than I wanted to go in this blog post so I'll be a little sparing on the details. My intention is to replace classes with a more powerful mechanism - topic maps. Allow knowledge based relationships to exist between objects instead of just named instance variables and suddenly you have a very powerful system. You can describe an object as taking the role of a class within a scope instead of simply saying "this object is a class". It also lets you model the other sorts of things you were interested in - but natively as what they are instead of saying "it's a class, but it's not a class". More on this another time.

By Michael Lucas-Smith on June 4, 2005, 12:40:54 am

Hi Brian, Good to hear from you. Slate news is always interesting and I like reading the website on occassion. To answer only one of your questions. Slots are only filled in by calling := or using the reflection API. A DNU will still occur because the slot you're attempting to access won't exist yet.

By Peter William Lount on June 4, 2005, 6:31:52 pm