Smalltalk vs. Javascript; Diff and Diff3 for Squeak Smalltalk
This page is a mirrored copy of an article originally posted on the (now sadly defunct) LShift blog; see the archive index here.
Tue, 1 July 2008
Many of my recent posts here have discussed the diff and diff3 code I wrote in Javascript. A couple of weekends ago I sat down and translated the code into Squeak Smalltalk. The experience of writing the “same code” for the two different environments let me compare them fairly directly.
To sum up, Smalltalk was much more pleasant than working with Javascript, and produced higher-quality code (in my opinion) in less time. It was nice to be reminded that there are some programming languages and environments that are actually pleasant to use.
The biggest win was Smalltalk’s collection objects. Where stock Javascript limits you to the non-polymorphic
for (var index = 0; index < someArray.length; index++) { var item = someArray[index]; /* do something with item, and/or index */ }
Smalltalk permits
someCollection do: [:item | "do something with item"].
or, alternatively
someCollection withIndexDo: [:item :index | "do something with item and index"].
Smalltalk collections are properly object-oriented, meaning that the code above is fully polymorphic. The Javascript equivalent only works with the built-in, not-even-proper-object Arrays.
Of course, I could use one of the many, many, many, many Javascript support libraries that are out there; the nice thing about Smalltalk is that I don’t have to find and configure an ill-fitting third-party bolt-on collections library, and that because the standard library is simple yet rich, I don’t have to worry about potential incompatibilities between third-party libraries, such as can occur in Javascript if you’re mixing and matching code from several sources.
Other points that occurred to me as I was working:
- Smalltalk has simple, sane syntax; Javascript… doesn’t. (The number of times I get caught out by the semantics of
this
alone…!) - Smalltalk has simple, sane scoping rules; Javascript doesn’t. (O, for lexical scope!)
- Smalltalk’s uniform, integrated development tools (including automated refactorings and an excellent object explorer) helped keep the code clean and object-oriented.
- The built-in SUnit test runner let me develop unit tests alongside the code.
The end result of a couple of hours’ hacking is an implementation of Hunt-McIlroy text diff (that works over arbitrary SequenceableCollections, and has room for alternative diff implementations) and a diff3 merge engine, with a few unit tests. You can read a fileout of the code, or use Monticello to load the DiffMerge module from my public Monticello repository. [Update: Use the DiffMerge Monticello repository on SqueakSource.]
If Monticello didn’t already exist, it’d be a very straightforward matter indeed to build a DVCS for Smalltalk from here. I wonder if Spoon could use something along these lines?
It also occurred to me it’d be a great thing to use OMeta/JS to support the use of
<script type="text/smalltalk">"<![CDATA[" (document getElementById: 'someId') innerHTML: '<p>Hello, world!</p>' "]]>”</script>
by compiling it to Javascript at load-time (or off-line). Smalltalk would make a much better language for AJAX client-side programming.
Comments
On 3 July, 2008 at 5:51 pm,
wrote:On 3 July, 2008 at 5:51 pm,
wrote:((I accidentally deleted a bunch of comments I didn’t mean to delete today, so I’m having to repost them manually:))
tonyg wrote:
Heh, yes — you’re right in that the scoping rules are better than they could have been; I guess I’m really missing more lightweight block scoping, a la Scheme. Smalltalk’s Blue-book scope rules are a little strange, too, but are an improvement over the current state of Javascript.
On 8 July, 2008 at 4:06 pm,
wrote:Its true that JavaScript can be non-intuitive for those who already have traditional bad habits and expectations, but I don’t see that as a fault of the language itself.
Its easy to shoot yourself in the foot with “this” if you aren’t used to it.
“this” referring to the current object instead of the current “class” is just different, not wrong.
For scoping, you could use the “let” keyword instead of “var” to define a block scoped variable instead of a function scoped variable.
Collections? you have a few choices but it really depends on the environment and the collection type
Arrays:
Array.forEach(function(element,index,array){})
object properties: (and hash tables)
for(var i in obj){
}
True collections:
var ej = new Enumerator([collection])
for (;!e.atEnd();e.moveNext()){
x = e.item();
}
On 8 July, 2008 at 4:08 pm,
wrote:typo:
var ej = new Enumerator([collection])
to
var e = new Enumerator([collection])
On 8 July, 2008 at 5:33 pm,
wrote:@TNO: You’re right that the lack of a decent library is not an issue with the language per se. The problem with “this”, though, is. It is rebound in an awkward way depending on how the function concerned is invoked, which makes programming in a functional or Smalltalk style awkward and error-prone, even for people perfectly well aware of the way in which it works.
Regarding collections, I agree it’s a library issue. Sadly, the default environment is almost empty. “forEach” is present in Firefox and Rhino, but not IE6. Enumerator is defined in neither Firefox nor Rhino (and I haven’t tried IE). The var-in-obj trick is an interesting idea, but still pretty awkward compared to the forEach approach (or even to Python’s generators).
Most disappointingly, it’s hard to improve the situation even with the help of extensions to the meagre default environment. “forEach”, for instance, runs smack bang into the problems with “this” not being properly closed over in function literals!
/* what is the value of “this” here? */
[1, 2, 3].forEach(function (v) { /* what about here? */ });
On 8 July, 2008 at 8:06 pm,
wrote:“this” is a contextual statement. Used in the global sense it obviously refers to the global object itself (WIndow, WScript, or whatever the environment defines).
http://developer.mozilla.org/en/docs/CoreJavaScript1.5Reference:Operators:SpecialOperators:this_Operator
The only place where things could get confusing is if you define a javascript “class” then try to call methods of the class internally. The workaround to that is to assign “this” to an internal variable when initially created to save the context before the context changes and before the “class” is instantiated by the “New Foo” statement
“this” within the forEach function seems weird only at first because I forgot to mention that you can pass a context to the method:
Array.forEach(fun,context)
If a context is not given the global context is assumed as you can see with your example.
(more info: http://developer.mozilla.org/en/docs/CoreJavaScript1.5_Reference:Objects:Array:forEach)
“for in” was defined specifically for handling object properties and hash tables.
var hash = {
“AK” : “Alaska”,
“CA” : “California”,
etc…
}
regular loops for specifically arrays
forEach bridges that gap into a single method
As for IE6 and IE7 not supporting it, its easy to implement:
if (!Array.prototype.forEach)
{
Array.prototype.forEach = function(fun /*, thisp*/)
{
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
fun.call(thisp, this[i], i, this);
}
};
}
The Enumerator object was defined by the Microsoft JScript engine for iterating over collections of a class (Which is obviously non-standard since ES3 does not have a concept of classes, but the new ES4 should fix this)
So ultimately hands down the issue with the language is the lack of a standard in 10 years. (which is finally no longer an issue:
http://www.ecmascript.org/
https://mail.mozilla.org/pipermail/es4-discuss/ )
On 8 July, 2008 at 8:54 pm,
wrote:I guess I could have given some examples of generic forEach usage..
var myHash = {
'zero':'a',
'one':'b',
'two':'c',
'three':'d',
'four':'e',
'five':'f',
'six':'g'
}
var myArray = ['a','b','c','d','e','f','g'];
//array loop
for(var i=0;i<myArray.length;i++){
alert(”index: ” + i + “, value: ” + myArray[i])
}
//hash loop
for(var j in myHash){
alert(”key: “+j+”, item: “+myHash[j]);
}
//forEach array
myArray.forEach(function(el,i){
alert(”index: ” + i + “, value: ” + el)
})
// forEach is an array method, not an object method but we can make one easy enough
Object.prototype.forEach = function(fun){
if (typeof fun != “function”)
throw new TypeError();
for(var i in this){
//this will enumerate over methods too, so to be careful:
if(this[i] !== this["forEach"]){
fun(this[i],i,this);
}
}
}
//forEach hash
myHash.forEach(function(el,i){
alert(”key: “+i+”, item: “+el);
})
On 9 July, 2008 at 8:33 am,
wrote:I’m not really interested in examining the problems with Javascript in a lot of detail here; that has been done in myriad other places on the ‘net, by others more eloquent than I. Javascript is useful in a browser context, and I will keep using it in that context, mostly because I have to.
I’m more interested in getting across the message that Smalltalk is a smaller, more consistent, less error-prone language, with a much better set of collections; that it was easier to get a higher-quality result out of Smalltalk in a shorter period of time; and that it would be very interesting to have a Smalltalk environment available inside the browser, in place of today’s Javascript environments.
On 9 July, 2008 at 3:59 pm,
wrote:I’m hardly trying to convert you or anyone else, but I think clarification and correction is needed. I agree with you that ES3 vs SmallTalk is a no brainer, and that alot of languages would have been preferable (I would have preferred Scheme). Hell, the first version of JavaScript didn’t even have arrays. But I think we should be grateful of the fact that Brendan Eich had the foresight to implement something with a proper paradigm to it instead of some crap like VBScript.
Smalltalk has simpler collections its true, but the last time I looked (2005), the Regular Expressions in Smalltalk were no bed of roses either.
If Smalltalk had the same history as JS I highly doubt it would have came out without a few broken bones and inconsistencies of its own. (Just look at Java vs J#, or the multiple different side-by-side implementations of ES3). I think it would have come out better than JavaScript did since it was implemented earlier and already had a standard behind it but the politics would have hurt any language dumped in that environment
Less error prone? I’ll agree with you, though I am pretty sure we’re thinking of different things when we say that. If SmallTalk was more popular and in more hands I would bet it would show more holes than it currently does.
A “better” language can’t be defined solely by its ability to do x and y, but it has to also be judged by the syntax it uses to get there and in how many steps. Syntactic sugar, proper default library and environment support and standardization are all key. What good is a language if no one implements it or wants to use it? (Paul Graham’s essay on a similar topic is a good correlation http://www.paulgraham.com/iflisp.html).
“it would be very interesting to have a Smalltalk environment available inside the browser, in place of today’s JavaScript environments.”
We both know that the odds for ANY language to replace JavaScript are slim to none. But I can bet you that once ES4 is finalized there will be a number interpreters on top of the JIT compiled ES4 code.
On 1 August, 2008 at 11:37 pm,
wrote:You should assist the Cobalt team over at
((I accidentally deleted a bunch of comments I didn’t mean to delete today, so I’m having to repost them manually:))
masklinn wrote:
since JS’ this is the most fucked up thing there is in the language bar none, it’s not surprising to have it blow up in your face.
Erm… javascript has lexical scoping and fairly sane scoping rules, if a little strange: scoping is lexical, but only functions create scope, regular statements (if, else, try, for, …) don’t. Also, not using “var” during an affectation will walk the scopes upwards, and if it doesn’t find the variable it’ll create one in the toplevel (you can ask FF to display a warning when that happens)