Smalltalk = strongly typed?

Blake blake at kingdomrpg.com
Thu Oct 14 11:15:44 UTC 2004


On Thu, 14 Oct 2004 00:09:06 -0700, Andreas Raab <andreas.raab at gmx.de>  
wrote:

> Hi Blake,

Thanks for responding Andreas!

> Weak vs. strong typing is not primarily about type-*checking* but rather  
> about whether an object (reference) uniquely belongs to a type or not.

Well, okay, but what's the purpose of having an object reference uniquely  
belong to a type if that never comes into play? OK, so foo uniquely  
belongs to class bar, but bar is never referenced, since foo responds to  
bie, bix and fee? I mean, at that point, what does "strongly tpyed" even  
mean.

Maybe what I'm talking about really is best called explicit typing, which  
I see as having the following benefits: The programmer explicitly states  
what he's using; the programmer explicitly states what he wants; the  
compiler can verify that what he's using matches what he wants. (This  
really is neither static nor strong, necessarily.)

> To give an example, in C[++] (which is statically but weakly typed) I  
> can do things like:
>
>     ((Foo*)0)->size()
>
> e.g., take an integer, "cast it" into a Foo* and invoke methods on it.  
> The weird thing is that those methods (as long as they aren't virtual)  
> may even work!

That kind of thing used to be possible in Borland Pascal (5.5-7.0).  
Actually, it's still possible because the old object model is still there,  
but you'd have to work at it. (A static method in a class model that  
allows for static objects. It's understandable why it works, even if  
there's never a valid reason to do it.)

> In Smalltalk there is no such thing, you cannot trick anyone into  
> interpreting a SmallInteger object as being of type Foo. It just won't  
> work. That said...

Well, sort of, right? If Foo and SmallInteger receive the same  
messages--oh, wait, okay, so, the point is that, yes, it could be used in  
the same place as FOO but could never be made to call FOO's methods.

<lightbulb goes on>

> ... your interpretation is entirely correct - if a Foo understands the  
> set of messages that a SmallInteger understands it will work  
> interchangeably with SmallIntegers. But while that may be true it still  
> remains a Foo!

I've got it! "The rain in Spain stays mainly in the plain."

> You are precisely right, except the "it's just a label" part. For the  
> advocates of static type checking the ability to coerce between types is  
> both a blessing and a curse. It's a blessing because without the ability  
> highly generic data types (like lists) are almost impossible to  
> implement yet, at the same time, it completely destroys the illusion  
> that static type checking would make your program "type-safe" in any  
> meaningful way[*1]. In other words, how can you possibly tell whether:
>
>     Foo *myFoo = (Foo*)list.getNext();
>
> will answer a valid Foo or not?

Well, Delphi handles it this way:

	if Foo is TList then myFoo := TList(myFoo).GetNext;

or, if you prefer:

	myFoo := (myFoo as TList).GetNext;

'course, you're not doing squat statically at this point. Arguably, you're  
getting the benefits of static type-checking when possible and dynamic  
when not.

	But MAN, it makes for some UGLY-ASS code.

	And, I hope everyone understands, that I'm not dissing ST or saying  
Delphi is superior. In some Delphi communities--well, okay, probably every  
programming community I've ever been in--I've been known to post things  
that garner the occasional response of "Well, if Smalltalk is so wonderful  
why don't you go use that?"

	(I use a lot of programming languages. About the only job I bever took  
that I occasionally regret turning down is one for IBM, who was using  
Smalltalk in the '94 winter Olympics.)

> Even worse, how can you say whether the coercion operation is even valid  
> (e.g., "understood") by the element you are retrieving from the list?  
> Note that the meta-point here lies in acknowledging that only the object  
> (receiver) can really decide which messages it (pretends to) understand.  
> As such any "external assumption" about the object is prone to fail and  
> gives raise to all of the "niceties" of the modern computer world,  
> including buffer overruns (hey, the compiler said it'd be okay!) etc.

	Well, that goes back to C/++, though, where buffer overruns are a way of  
life but, for some reason, LINT isn't.<s>

> [*1] Or at least I haven't seen any such meaningful interpretation. It  
> is true (and I won't deny it) that static types help you avoid a few  
> mistakes by sending objects messages that they don't understand but in  
> reality[*2], if that happens it means you haven't tested your code. But  
> then at least you know what you're up against contrary to the situation  
> where you have a foo cast into a Bar and wonder how it could have  
> possibly gotten into that state. I have debugged programs where this has  
> happened and I take a few DNUs every day single over that (in particular  
> considering that I can fix them right in the debugger ;-)

	Well, I've been reading people's arguments that type CHECKING can be done  
without static typing, and it seems reasonable (if way more complex). It's  
more of a "hey, you sure about this" kind of checking than a "I won't  
compile it" deal, but maybe that's enough.

	It seems to me that a compiler could build ad hoc interface requirements  
and check methods against the parameters passed to them. Except for this:

> [*2] The ONE good reason for static types is auto-completion btw. This  
> really sucks without adequate type-information but I'm still thinking  
> that a decent type-inferencer could do a perfectly good job here.

	OK, thinking out loud here, but, when using an object, there are two  
possibilities: Either you created it locally, and therefore you know what  
type it is from that, or it was passed as a parameter--and you should know  
how it is going to be used. The local situation is trivial:

i = 1 'okay, you know i is an integer, and can auto-complete for integer  
methods

For a routine, you could say something like:

myRoutine: t1#Integer

Then you could auto-complete, though you're really more interested in an  
interface than a specific inheritance. No reason to require Integer, if  
you're really okay with just Magnitude--and no reason to require all of  
Magnitude, if you just want "<=".

	Sort of a catch-22: You need to know what you want in order to specify  
the interfaces, but you want to specify the interface in order to allow  
the environment to help you fill in the interfaces.<s>

	I suppose one possibility would be for you to put in the base type you  
had in mind (like integer) and then suggest an ad hoc interface when you  
finished the routine:

myRoutine: t1#Integer(supports <=, +)

	I kind of like that, though not the punctuation of it.<s>

	I see the evolution of languages and environments needing to move toward  
respect for the distinctions between design-time and compile-time and  
interface interaction. I'm not sure the Smalltalk approach of "it's all  
design time--or run-time, whatever"<s> is optimal.

	===Blake===



More information about the Squeak-dev mailing list