archives

beyond programming languages

How's this for a research agenda:

As computer technology matures, our growing ability to create large systems is leading to basic changes in the nature of programming. Current programming language concepts will not be adequate for building and maintaining systems of the complexity called for by the tasks we attempt. Just as high level languages enabled the programmer to escape from the intricacies of a machine's order code, higher level programming systems can provide the means to understand and manipulate complex systems and components. In order to develop such systems, we need to shift our attention away from the detailed specification of algorithms, towards the description of the properties of the packages and objects with which we build.

Ruby vs. Smalltalk

Hmm. No headers (<h1>, <h2>, etc.) allowed in here. Use COBOL-style level numbers to express an hierarchy of headers?

01 Semantics

Ruby has mixins; Smalltalk does not. Mixins help much in design.

Ruby permits adding methods to individual objects; in Smalltalk, all methods reside in classes.

In Ruby, it is practical and somewhat useful to add methods dynamically; in Smalltalk, the practice is generally to treat the methods and classes as static.

Ruby offers powerful macros in class definitions; Smalltalk offers no macros at all.

01 Syntax

02 Literals and Constructors and Destructors

03 Array Constructor

Ruby offers a very convenient syntax for constructing an array from expressions for its elements. I think all modern Smalltalk implementations also support this, although it is relatively new in the history of the language. If a Smalltalker reading this knows of a Smalltalk implementation in commercial or wide use that doesn't support the syntax "{foo. bar. bletch}" (note that there are spaces after the periods here; these are sentence-end periods, not namespace-qualification periods) for constructing an array, equivalent to "(OrderedCollection new add: foo; add: bar; add: bletch) asArray", please add a comment to that effect.

In array construction in Ruby, you can say something like "[foo, *bar]" to mean the array whose first element is given by the expression foo and the rest of whose elements are given by the array given by the expression bar. In straightforward Ruby not using the "*", this is "[foo] + bar" (+ is overloaded for concatenation in Ruby's library). In Smalltalk, it would be "(Array with: foo), bar", where the comma operator is used for concatenation in Smalltalk's library. Interestingly, the "*" syntax can also be used for destruction. You can say, for example

[foo, *bar] = bletch

where "=" is the assignment notation (discussed below) and bletch is an array of at least one element. Then foo will be set to the first element, and bar will be set to the rest. This example emulates Joy's "snoc" operator (cons spelled backward). See also, multiple assignment, below. Smalltalk has nothing like the conveniences offered by Ruby's prefix "*" notations.

Ruby also lets you use the prefix "*" in argument lists to construct argument lists and in parameter lists to deconstruct argument lists. Perhaps as a Smalltalk advocate in regard to keyword arguments vs. positional arguments, however, I might not think this usage brings much to the party as an advantage of Ruby over Smalltalk. It does, in the Ruby context, bring something to the party, just as the equivalent construct in parameter lists in Lisp ("&REST") makes Lisp much more flexible. Who is to say whether positional parameters should be basic and keyword: value pairs should be packed up in a dictionary and passed as one of the positional arguments, or conversely, whether keyword parameters and arguments should be basic and if you want to pass a list you pack up the list as an array and pass it as one of the keyword arguments? Smalltalk takes one attitude and Ruby the other. Personally, I'm in favor of keywords.

03 Dictionary Constructor

Ruby offers a very convenient syntax for constructing a dictionary (a dictionary is called a Dictionary in Smalltalk and (unfortunately) a Hash in Ruby; I'll use the term dictionary as the generic term). The constructor is short to write, and keys and values can come from any expressions. Example, "{:foo => :bar, :bletch => 3}", which in Smalltalk would be "Dictionary new at: #foo put: #bar; at: #bletch put: 3; yourself". Note how much shorter "{:foo => :bar, :bletch => 3}" is than "Dictionary new at: #foo put: #bar; at: #bletch put: 3; yourself". This leads to significant economy of writing, if you are passing around many constructed dictionaries, which in Web development, for example, tends to be done quite a bit. I'll preempt whoever is going to point out that much of the dictionary construction in Ruby is being done to overcome the lack of the keyword message call (discussed below), by saying that dictionaries have other uses than immediate use in a message call. Sometimes you would like to pack up the message and treat it as a whole. Oz/Mozart recognizes this as Ruby does (although details differ).

03 String Constructor

Ruby offers a convenient syntax for constructing a string. Parts of the construct can be literal, and parts can invoke expressions for string values to be interpolated into the string under construction. Example: Ruby:

    "Hello, #{name}!"

Smalltalk:

    'Hello, ', name, '!'

At first glance, the reader might not see much economy in the Ruby syntax over the example I gave in Smalltalk; however, if you are writing many constructions of strings from alternating literal and variable parts, the Ruby syntax becomes pretty convenient. I think it is less error-prone than a long sequence of concatenations given explicitly, with many literals interspersed. Moreover, experienced Smalltalkers know that abuse of the comma operator can lead to significant inefficiencies, where abuse consists of building up a long string (e. g., a report) via many concatenations. The Smalltalk library implements the comma operator inefficiently by copying over the string every time. Consequently, we get in the habit of writing an example like this this way:

WriteStream new
    nextPutAll: 'Hello, ';
    nextPutAll: name;
    nextPutAll: '!';
    contents

(the semicolon is used in Smalltalk for the cascade syntax; it means send multiple messages to the same receiver). If Ruby's concatenation (uttered as "+") is ever found to be inefficient like Smalltalk's, I'm sure that what the language culture would lead to is making it work as efficiently as though implemented by Smalltalk's WriteStream, not to inventing a new class equivalent to that class in Smalltalk. The general attitude difference seems to prevail, that in Ruby it's more, write what you mean, and leave efficiency to the language (or library) implementation.

03 Regular Expression Literal

Ruby offers a very convenient syntax for expressing patterns over strings, i. e., regular expressions. It looks just like in the Unix "ed" line editor, with the pattern between slashes. Smalltalk has nothing of the kind, in its syntax.

03 Alternative Syntaxes for Literals

Ruby has several other literal syntaxes for convenience, that Smalltalk does not.

02 Assignment operator

Smalltalk's assignment operator is ":=", which looks unambiguously like assignment and not equality, which I consider very appropriate for an imperative language. The Ruby designer (who nevertheless I thank profusely (if he's reading this) for the good features of the language, which I think occur together in no other, so the combination of them makes for a helpful innovation, and for providing the first interpreter) erroneously chose to follow Fortran and its kin in using naked "=" for assignment.

On the other hand, Ruby offers multiple assignment. You can swap two variables by saying "a, b = b, a". In this vein, see also the prefix "*" notations I mention above. Smalltalk does not have multiple assignment.

Ruby offers convenient translations of assignment syntax into message calls. Smalltalk: "foo at: x put: y". Ruby: "foo[x] = y". You write what you mean, not how it is implemented. Smalltalk: "aircraft altitude: 3600"; Ruby: "aircraft.altitude = 3600". In each of these examples, Ruby translates the assignment into a call that can be fielded via the normal method dispatch mechanism.

02 Message-call syntax

03 Keyword Messages

Smalltalk has keyword syntax for putting arguments (and parameters) in the middle of a selector; Ruby lacks this feature. I think this is one of the best contributions of Smalltalk to the history of programming languages. Keywords make a program much easier to read, I think, because they tell the reader the meaning and intent and significance of the parameters and arguments, in the sense of their participation in the design behind the program. In Smalltalk, the keywords contribute to the selector, so they participate in the message dispatch. This supports the writing of distinct methods for handling different cases of what data are available to construct or calculate some desired result.

Ruby somewhat makes up for the lack of keyword message syntax with the dictionary constructor syntax. In fact, in the context of a message call, Ruby makes the syntax for specifying keyword-argument pairs even easier, almost comparable to Smalltalk's keywords. Let's look at an example. Consider, in Smalltalk:


    viewer cueMenuRoleNamed: aName forPage: aPage andLayout: aLayout

In Ruby, we could write:


    viewer.cue_menu_role :role_name => a_name, :page => a_page, :layout => a_layout

In this Ruby example, the programmer was able to omit the braces that would otherwise have to enclose (and form part of) the dictionary constructor because the dictionary is the last argument and the syntax allows that omission in that circumstance, as a notational convenience. Note how closely the Ruby example parallels the Smalltalk. But there is an important difference in the semantics here. In the Ruby example, the selector is only "cue_menu_role". If the "cue_menu_role" functionality is to operate with different combinations of given data in addition to the selection exemplified (role name, page, and layout), the method or methods that implement "cue_menu_role" will have to examine the argument list and distinguish the cases. In the Smalltalk example on the other hand, the selector is "cueMenuRoleNamed:forPage:andLayout:". The selector embodies the selection of available input data, so the method that implements it only has to deal with that case.

In favor of the Ruby approach, on the other hand, is the fact that in Smalltalk, the order of the keywords matters, but in Ruby it does not. Usually in the English that the programmer is trying to get the reader to think of as the interpretation of the operation, the order doesn't matter, so for example, it's a nuisance to have to implement both ifTrue:ifFalse: and ifFalse:ifTrue:. However, I suppose there are counterexamples in the underlying English. Sometimes, order matters in English.

So, in looking at keywordism as a whole as addressed in the two languages, I see some strength in Smalltalk in that the keywords are part of the selector, and that the use of keywords with message arguments right at the point where you want to trigger the dispatch is syntactically simple and easy. I see as weaknesses in Smalltalk that the order matters, and that it is not so easy to package up the collection of keyword-argument pairs as a message that can be treated as a whole.

03 Syntax Between Receiver and Selector, and Treatment of Juxtaposition in the Syntax

In Ruby, you say "foo.bar"; in Smalltalk, it's "foo bar". The notation for a unary message call is cleaner in Smalltalk than it is in Ruby, because simple juxtaposition, I'd say, requires less mental effort to read, than the notation using the dot. Of course I don't have any cog-sci data about mental effort; I'm just saying that it feels cleaner to me.

The syntax makes better use of juxtaposition in Smalltalk. In Ruby, juxtaposition has been used to separate selector from arguments. For example, "foo.bar bletch, baz" would mean what "foo.bar(bletch, baz)" means in Ruby. This is arguably not a terribly wasteful use of juxtaposition in Ruby's syntax. It makes for a clean, "command" look, especially in cases where the receiver is implicitly "self" (discussed below). "load 'foo.rb'", for example, looks cleaner than "load('foo.rb')". But the direction of development of Ruby seems to be away from a consistent use of juxtaposition in this way. If you write, for example, "foo (bar)", the complier might warn you that you ought to remove the space to anticipate the language's future. So, in summary about the dot and juxtaposition, I say that although the use of juxtaposition in a command like "load 'foo.rb'" is quite nice, Smalltalk rather than Ruby has made the better choice about where to harness simple juxtaposition in the syntax, in that Smalltalk grabs this for its basic message-call syntaxes, to separate receiver from selector.

03 Implicit Self

In Ruby (as in the language Self, the pioneer in this regard, and from which the language Self gets its name), you can usually abbreviate message calls on "self" by omitting the mention of "self". For example, for "self.foo" you can write simply "foo". But in Smalltalk, you cannot abbreviate "self foo" by writing simply "foo". This makes a major economy in writing and reading Ruby code.

03 Strange Syntax for Blocks

In Smalltalk, there is easy syntax for constructing a closure and passing it as an ordinary argument and picking up that argument with an ordinary parameter and then for invoking the closure and/or passing it on. Ruby, semantically, has the same capabilities for constructing, passing, and using closures. However, Ruby uses oddball syntax for these things, for no benefit that I can discern. There is a specially distinguished argument position available in a call, specifically for passing a closure. If you are going to construct a closure in that position, the syntax is simple. But the closure in its simplest syntax is not an expression. It only goes in that special position in the syntax, or has to have a word put in front of it, "lambda", to be turned into an expression that could be put in an ordinary argument position or any other expression context (e. g., right side of assignment). If a method is expecting to receive a block as an argument, the designer has to choose between having it take the block as an ordinary argument or as the special block argument (either can be chosen, in the monomorphic case). And if the method needs to take two blocks, at least one of them has to be passed as an ordinary argument, so the decision has to be made whether to pass one as the special argument and if so, which one. Conversion in both directions is available in all contexts where it makes sense, between the special argument or parameter and an ordinary expression. I think this exceptionalism in Ruby's syntax imposes significant extra conceptual load to understanding the syntax. Smalltalk's treatment of blocks is plenty economical, whether they are being passed as arguments right at the point of construction or not.

I guess I can see why this exceptionalism arose; it avoids having the closing parenthesis of the argument list coming right after the end of a block, which I can see would look ugly. But, so much twisting and turning and squirming for a tiny increment of beauty.

03 The Cascade

Smalltalk has the cascade syntax, which Ruby lacks. Perhaps this lack is not as costly to Ruby programmers as it would be to Smalltalk programmers if the cascade syntax were dropped from Smalltalk, because in cases where the receiver is self, a cascade would buy nothing in Ruby because you can leave out the receiver in those cases anyway.

03 Special Characters Permitted in Selectors (speaking here of selectors other than "+", "*", and the like)

Ruby permits "!" in selectors, which is useful to warn code readers that a functional style is not being used. Smalltalk allows only alphanumerics in its unary selectors and in the keywords of its keyword selectors (not counting the colon, which has a specific role in the syntax even though the jargon of discussion of Smalltalk usually speaks of the "keyword" as including the colon). Ruby also permits "?", but below, I argue that this doesn't help because I prefer Smalltalk's convention for choosing selectors indicating Boolean results.

02 Control Constructs

Smalltalk's syntax includes no control constructs except the "^" (early return). Any kind of "if", "while", etc., are made by message calls using blocks. At least, syntactically they appear that way. As the Self designers point out, in reality you can't override ifTrue:ifFalse: unless the Smalltalk compiler resorts to the sophistication characteristic of the Self compiler. If you could, many programs would run too slowly because they are laced with much conditional code. But anyway, syntax-wise, the control constructs don't have their own syntax in Smalltalk, other than the "^". Ruby on the other hand, includes syntax for a number of common control constructs even though blocks (closures) are almost as convenient to express in Ruby as they are in Smalltalk (but not quite; sometimes you have to throw in an extra word to make the block an expression). When I was a religious Smalltalker, I might have laughed at languages with control constructs and praised Smalltalk for its syntactic simplicity in using pure object orientation to express the control constructs. But I can also see a Rubyist's point that these control structures are in common use in all imperative programming, so why not have the syntax make it straightforward for a programmer to just write what she means? It doesn't cost much.

02 Returned Value

Mentioning "^" above reminds me, that in Smalltalk, if you don't explicitly write "^" with an expression (which means, return the value of the expression as the result of the call), a Smalltalk method returns "self" (I mean, what "self" means in the languages, not the string "self"). In Ruby (as in Lisp), the last expression in the method gives the value returned (absent an early return). This supports a functional mindset; I have written any number of methods that consist of just one expression. Also, it says that if you are not doing an early return, which is in fact a control construct that violates Dijkstra's structured programming, and that should stand out as a warning to readers, you don't have to write "return". I'm not saying I'm against early return; it can be very convenient and can contribute greatly to brevity. I'm saying, early return should stand out. In Ruby you can make it stand out by uttering "return" only in the case of early return. In Smalltalk, you have to write "^" something or you'll get "^ self".

For any Smalltalker who argues in favor of the programming style that says a method (or rather the whole suite of methods implementing the same selector) should be either designed to return a value or to have a side effect but generally not both, I agree; sometimes I prefer that style. And Smalltalk's default supports that style. But in Ruby, you can, with ease and clarity and economy, stanch any accidental return of a value some other programmer might seize on, by just ending your method with "true", having it return the Boolean true to indicate success in achieving the desired side effect. I think Smalltalk's support of returning self by default is more costly than beneficial, because I think the two considerations in the paragraph just above outweigh the advantage of support for ask-or-tell-but-not-both-at-once style via the default.

On edit: A comment below by user "renox" refers to a lesson from E, that there can be significant hazard of accidentally returning an object you don't want to. I accord that argument much weight, and I question the opinion I gave in my initial version of this section, strongly in favor of having the last expression be the returned value by default. Anyway, that is one of the differences between Ruby and Smalltalk, and it affects the flavor of the way they read, I find.

01 Declarations

Smalltalk requires declaration of all local variables and instance variables. In Ruby, you introduce both simply by usage; Ruby does not require declarations for them. This makes a major economy in writing in Ruby.

01 Scope

Ruby absolutely makes the wrong choice by denying the programmers any way, just in the syntax in a block of code as you could observe without looking outside the block, to specify a local variable scoped just to that block. You can write, for example, "foo" as the name of your variable, and you can limit its use to the block, and you can hope it will be scoped locally to the block. And indeed it will, if no one writes an assignment to "foo" above the block. In Smalltalk, you can easily declare a variable local to the block (although one or more older Smalltalk implementations would scope the variable wrongly at runtime; I hope all modern Smalltalk implementations have cleared this up).

01 Practices and the libraries

02 Choice of Selectors

As reflected in the libraries used widely in the respective languages, Smalltalkers make better choices of message selectors than Rubyists make. For example, I like "isNil" better than "nil?", and "asString" better than "to_s" (which means, "to_string"; I'm nitpicking here about the "to" vs. "as" rather than about the abbreviation of "string" as "s"). All the question-mark selectors can be expressed in a more English-like way by using the conventions Smalltalkers have coalesced around, using "is", "includes", or other verbs. "as" selectors express a more functional attitude, and "to" selectors express a more imperative attitude.

02 Array Stores

Smalltalkers choose on the basis of efficiency, whether to store a sequence of values in an OrderedCollection (for the non-Smalltalkers among the readers, note that in Smalltalk, "OrderedCollection" denotes a particular implementation, not a type -- not every ordered collection is an OrderedCollection) or an Array. Rubyists just use the Array type (it's a class, but it might as well be a type). If they need to add to the end of it, they just do.

Smalltalkers use SortedCollection if they want their data sorted, and the class makes sure to keep the instance sorted through its history of mutations (by and large); rubyists just tell the array to sort itself when they want it sorted.

So, in practices around arrays, the Ruby world looks simpler. Programmers face fewer choices, and fewer concepts to learn. You just write what you mean.

01 Private Methods

Ruby enforces declared privacy of methods at runtime. Smalltalk implementations I used do not attempt this. If I recall correctly, Dolphin does. As a Smalltalker, I treated the software-engineering declaration that a method is private, simply with discipline, not looking for enforcement support from the language itself. Ruby backs away from enforcing method privacy when the call is indirect via "send", and taking advantage of this is the only way to do certain useful metaprogramming. I wish the relevant methods were public. Bottom line, maybe others will argue strongly for or against privacy enforcement; I guess it's not a big deal to me.

01 Development Environment

Every Smalltalk implementation except the Gnu one offers a development environment where your data can be present along with your software to be modified. You can set up conditions very quickly and try out code, and examine it in any intermediate state of execution, and modify the code at any point, and so on, as pointed out elsewhere. No Ruby implementation so far offers this. Ruby practices depend on the file system as the repository for the software; Smalltalkers use the image. Either practice may fall back on a different repository for the real code repository for version control and sharing with other programmrs (Subversion, or Envy, or whatever), but I'm talking about the development environment here. Smalltalkers look at their code through the class browser. The source code seems to live in the environment along with the values of variables, etc. The practice with Ruby is to organize the code in a file system. I think Smalltalk has it all over Ruby in this regard. It's great to see the source code of any method instantly, and browse the senders or implementers of a given selector.

01 What the language environments could easily learn from each other

I submit that Ruby could learn the class browser and browse senders and browse implementors and the whole image with the data and software being present at the same time, the easy debugging and incremental tracing and trying code on the fly and changing it on the fly, all that, from Smalltalk. And that Smalltalk could learn to put some macros in the class definitions. Just keep the source code for the class definition (excluding the methods), as one snippet of text (or maybe more that one, if you have a module system where different modules could contribute to the definition of "open" classes), keep that snippet of source code along with the class (or in modules if you go there), make it visible in the browsers, and every time it is touched, execute it again. Some practices would have to be added to both languages so for the generated consequences of running the macro calls in the class definition would not survive when you edit the definition. Yes, there are issues with how that affects existing instances of the classes, but the languages could solve that more or less the way Smalltalk handles schema evolution today. All the metaprogramming that Ruby supports that make those macros work well, plus the modules and mixins, and the adding methods to objects dynamically, I think that those could be added to Smalltalk without disrupting what Smalltalk already is and does. None of these changes would require touching a parser or a compiler in either language environment, nor the virtual machine that runs either language. Such changes fit with the basic class (or "behavior") model shared by the two languages. I suspect I could sit in a Smalltalk image right now and look at how Class and Metaclass and ClassDescription are subclassed off Behavior, go back to basic Behavior, subclass it differently and implement Ruby's modules, mixins, and the attachment of methods to objects, basically the same way Ruby does it. The languages as implemented have no fundamental difference in their virtual machines. Under the hood, Ruby puts all methods in behaviors just as Smalltalk does. She simply gives any object that has custom methods, a special "virtual" behavior (class), which inherits from the object's nominal class. Just as easy to do in Smalltalk.

01 Paid Work

I am glad to see Ruby on the scene, because as a very Smalltalk-like language semantically, with even some advances over Smalltalk both in semantics and syntax, and as a language that judging by the count of ads on Monster and the like, is catching on somewhat in the corporate world as Smalltalk itself dies out, seems as though it might be the rescue vehicle for Smalltalk-like languages as a professional tool. A fine development to tide me over until the whole programming world switches to declarative programming (or dies out due to the end of oil, but that's another subject).

01 Web Support

Ruby has Rails and Smalltalk has Seaside. These are not the same, so I understand.