Just Smalltalk

Why I Am Not An XPer

March 2, 2006 19:23:55.785

(with apologies to Bertrand Russell)

This really is more like Why I Don't Call Myself An XP Smalltalker but it doesn't have quite the same ring. Inspired and prompted by the recent discussion of method comments on c.l.s.

Somehow in Smalltalk, the birthplace of extreme programming, it's often pretty much taken for granted that in order to be an eXtreme Programmer one has to stop writing method comments. Because "if a method needs comments it needs to be rewritten", or because "good code is self-documenting" or because "comments lie, code doesn't". Or even because "Beck teaches so". Indeed this is what we Smalltalkers were told when it was all just starting.

I say that is bullshit.

I often deal with code written by XPers, and excellent Smalltalkers at that, so this happens to be a rather informed opinion.

Don't get me wrong, I have the highest respect for XP and the values and practices it promotes. That's all great stuff. I do it all as much as I can. Except for the part about avoiding comments. But I heard that you don't do XP unless you follow all of XP practices--so I guess I can't say that I am an XPer.

I wonder though why something so admirable encourages some do things that create difficulties for others. We humans are pretty good at interpreting admirable ideas to justify slaughtering our brethren and wiping out civilizations, so I got curious if these same ideas might be interpreted differently outside of Smalltalk subculture.

Let's start with Wikipedia article on Extreme Programming. "Beck, Chrysler, Jeffries, Smalltalk". Good. Principles: "feedback, assuming simplicity, incremental changes, embracing change". Excellent. Activities: "coding, listening, testing, designing". Yep, let's do that. Practices, 12 of them: "pair programming, planning game, test driven development, whole team, continuous integration, design improvement, small releases, coding standard, collective code ownership, simple design, system metaphor, sustainable pace". No "no comments" so far--sign me up! "Controversial aspects". Aha, it must be hiding there somewhere... "informal change management, tests and requirement specs, incremental definition of requirements, pair work, no up-front design, having a customer representative". That's it? Where is the "uncommented code" thing? Let's search the page, I must have missed it somewhere. Nope, nothing there.

Alright, Agile Manifesto then. It's quoted right at the top of Wikipedia page, this must be the Sermon on the Mount. Principles of Agile manifesto rings the bell. Hmm... "satisfy the customer"... "harness change"... "work together".. "build projects around motivated individuals. give them the environment and support they need and trust them to get the job done". Darn, I love it! Sign me up! But where does it say "writing method comments will grow hair on your palms"?

Let's look at extremeprogramming.org then, under rules and practices. Coding rules. Hmm, again nothing about not writing comments. Let's ask Google if there is anything at all about comments on that site... nope, not about the kind you put in the code.

How about Ward's Wiki. Aha, there it is at the bottom of the page, a comment from Lars Olson, "It'd be selfish just to code and never document or comment on how the software works". Lars my friend, you are speaking my mind! Let's just search the wiki for the word "comment". Quite a few pages there. Hmm, the sentiment doesn't look anti-comment at all!

Could Smalltalk XPers be so anti-comment because of the early XP propaganda? The only expressly anti-comment page I found was written by Ron Jeffries nine years ago, according to the footer. Now when XP pioneers are no longer the rebels fighting the establishment deep in the jungle, when they are comfortably settled in their downtown lofts with checks coming from book publishers, why doesn't anybody pick up a fight about comments anymore? Could it be that some Smalltalkers are out in the threnches fighting a battle when the others declared a truce years ago? Or could it be--I am only exploring a possibility here--that it's a too-convenient-to-give-up excuse for avoiding the "boring stuff" at the expense of the next guy?

Here is my beef with comments, or lack thereof. It's communication, or lack thereof. I am coming back to my usual mantra. I am a usability guy, and I have my look at things. APIs are user interfaces. Programming languages are user interfaces. Programs are user interfaces. A program is not text that makes a stupid computer do stuff. It's text that tells another human being how a stupid computer does stuff. A program is a communication artifact.

Unsuprisingly, communication is a big thing in XP. It's there everywhere--in having a customer representative, in feedback, in pair programming. How could it happen that in such a communication-centric culture one of communication tools could be marginalized? (It seems that this applies to XP/Smalltalk more than to XP in other languages--is it true?)

The usual answer I hear from Smalltalkers is how refactoring and pair programming take care of communicating how program does stuff, obviating the need for documentation. I have two pins to burst this bubble.

Pin #1, for the "Pairing for communication" myth. Pair programming, communication, and together with them, XP, all stop when you throw your code over the fence at the next guy to deal with it. Stuff like that happens all the time, you know.

Pin #2, for the "Refactored self-documenting code" myth. Yes, you can refactor any given method to self-document what it does. The problem is, most of the stuff worth comminicating about a program is way above what any single method does. Take your head out of that refactored method and look around. I want to know higher-order architectural patterns you used to structure your system. I want to know the logic that links together those four lists up there. How can your self-documenting methods explain that? Yes, I can reverse-engineer them (that's what it amounts to) and find out. Is that communication that supports "my need and thrust to get the job done"?

Here is another reason I am leery of the "no method comments" policies. Jeffries admits even back in '97 that higher-level documentation like class comments is useful. Right there he also admits they were not good enough at keeping class comments current despite believing that they were useful. Why is that? I think it's the "broken window" effect from criminology. You leave a building abandoned, and for a while it's ok. Someone breaks one window, and soon all the other windows are broken too, junkies start shooting up inside and one night someone sets the building on fire. That first window is the trigger, a signal that anything is okay. Could the "don't bother with method comments" attitude be that first broken window for at least some projects or individuals?

Maybe someone will run queries on the open repository to find out.

As anecdotal evidence, look at RB add-ons like ThreePaneSelectorBrowser or back/forward navigation buttons by Eric Winger. RB is written by very smart people. It's all excellent Smalltalk code. How does it fare as a communication tool? Most authors say their add-ons "mostly work" but there are "some quirks" and they should ideally be redone by "someone who knows better how to fit them into the RB framework". Will that someone please stand up?

There is a saying in usability, "Know thy users for they are not you". No matter how refactored your code is, no matter how self-documenting it is, no matter what a cool XP dude you are--your mental perspective as the author is different from that of your readers. You know the stuff they don't, the stuff you understood as you were perfecting the design, the stuff you would have told them if you were sitting next to them while pair-programming to explain what's going on around and above the method.

Put that stuff in a comment.

Just Smalltalk

The Slow Interpreted Language

August 2, 2005 1:21:55.848

Philip Greenspun said that any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified bug-ridden slow implementation of half of Common Lisp. Or, I say, of half of Smalltalk.

By accident, I came across this post about speeding up message dispatch in Objective-C. As a benchmark, they measure the number of message sends per second. (Four seconds, actually). In benchmarks of this kind, with simple code run in a tight loop, Smalltalk often comes across as lagging behind "real" "industrial" languages with static type checking. Which is often used as a proof of the value of static type checking and "hard" compilation. Smalltalkers then have to point out how silly little tests are not representative of the actual performance in real life applications, and how programmer freedom modeling complex problems is a big factor in delivering those applications in the first place.

But, it's not all that simple in Smalltalk execution. Seeing that benchmark, I thought it would be fun to see how "hard" compiled languages measure up against Smalltalk's dynamic translation and inline caching in implementing message sends, given that a jump is cheaper than vtable indirection, not to mention a dispatch function. So I compiled and timed the Objective-C example and its C++ equivalent against a similar code fragment in VisualWorks.

First a few words about the implementations. The C++ code was the same as Objective-C, with the obvious change of Objective-C interface declaration to a C++ class. The increment() function in the class was virtual, since only virtual functions in C++ come anywhere close to what we can honestly call an OOP message send. Perhaps a C die-hard would argue, but to a Smalltalker there is no question about which is the real thing. The Smalltalk implementation incremented the counter by 2 each cycle rather than 1, since unlike Objective-C and C++, the #+ inside the #increment implementation is also a real message send.

The results on my machine (G4 1.33), in millions of message sends per second:

Objective-C:   5.7
C++:              17.8
VisualWorks:  31.6 

The tuned Objective-C version was said to gain 36%, which would be about 7.7 on that scale.

Update on Tuesday, 9:30

Ouch. I guess I shouldn't be doing benchmarks at 11 pm. I didn't notice that the C code was already dividing the results it printed by the number of seconds, and divided them again manually. Did I ever feel this stupid... So the actual results are:

Objective-C:   22.8
C++:              71.2
VisualWorks:  31.6 

Well, we still beat Objective-C, and then there is that point about silly little tests and modeling complex domains...

Just Smalltalk

Macros and Closures

April 3, 2005 1:47:56.812

This started as a comment to Michael's post comparing blocks and macros, but ended up long enough for an independent post.

I've used my share of both Lisp macros and Smalltalk blocks, and in my opinion arguing which is better is like arguing whether a screwdriver is better than a wrench. They are quite different tools which are sometimes used for the same tasks. Neither is fundamentally "better" than the other.

Blocks are language entities. Lisp has them too, assuming that by blocks we mean closures--which they are in modern Smalltalks. The only difference between closures in Smalltalk and Lisp is their syntactic form--which leads to substantial usability difference, in cases when they are used for delayed evaluation in control structures. It is possible to write control structures using "raw" Smalltalk blocks. In theory, it is possible to do the same in Lisp, but writing "(lambda () ...)" for each conditional clause just doesn't work as well as writing "[...]". Macros are used for control structures in Lisp not because someone decided they were better than blocks, but because Lisp blocks are not very usable on their own for that purpose.

Macros go deeper than blocks. They are language implementation entities--hooks at the post-parsing stage. They let you to do much more than inline some code, and their fundamental purpose isn't optimization at all. In fact, in some Lisp implementations, special forms implemented as macros expand into some structures made of closures, so they certainly aren't after any cheap performance gains there.

The purpose of macros is extending or even redefining the language. Imagine a method where I want to hook up a number of AspectAdaptors to an object, and store them all in instance variables. In Smalltalk, the best you could do factoring out adaptor creation would be something like

  nameHolder := self aspectAdaptorFor: #name on: object.
  addressHolder := self aspectAdaptorFor: #address on: object. 
  ... 

With macros, you can extend the language with a concept of "create an adaptor and assign it to a variable with a standard name". With MACROLET, you can control where that extension is available. So, you could make the equivalent code look something like

  (setup-aspect-adaptor object name)
  (setup-aspect-adaptor object address) 

No matter what, without macros you could not reasonably create abstractions that assign things to variables by generated names, or that do not require quoting according to "hard" language rules.

In other words, with macros you can tailor the language to do exactly what you want to do, in a single command. Without macros the best you can do is neatly arrange stock language constructs to map more or less closely to what you are trying to do. Smalltalk, thanks to its exceptionally well-designed syntax, lets you get pretty far along this route. So far, in fact, that macros end up not being worth the complexity they introduce. Smalltalk-72 by its design had macroexpansion as part of its execution model. It was abandoned later, and not only for performance reasons.

Just Smalltalk

CamelCase Variations

September 7, 2004 2:14:48.586

The "camel case" puzzle Travis posted interested me so much I figured I'd make a separate post about it, with some "value added" in the form of a few less practical but more interesting solutions. Warning: this is going to be a long one.

The "camel case" problem is finding "the tricky/elegant/terse/cool way" of splitting string made of words stuck together with word breaks indicated by case changes, like typical Smalltalk variable and method names. A complicating requirement is to do the right thing for acronyms or all-cap words, so that 'TheNextBIGThing' would be split as #('The' 'Next' 'BIG' 'Thing').

There is a convenient SequenceableCollection method in VisualWorks, #piecesCutWhere:, which evaluates the argument for all the pairs of adjacent characters in the string (1st and 2nd, 2nd and 3rd, etc) and returns a collection of substrings, broken between those pairs for which the block returns true. As an illustration, here is the simple (no acronym support) camel case splitter, as a method of CharacterArray:

  splitSimpleCamelCase
    ^self piecesCutWhere: [:each :next | each isLowercase & next isUppercase]

Here, we split the string at each case "uptick", and that's enough to produce

'splitSimpleCamelCase' => #('split' 'Simple' 'Camel' 'Case')

To take care of acronyms, we need a more complex split condition. Besides splitting at each uptick, we also need to split before downticks: in 'FOOBar', there is a downtick between $B and $a, so we need to split before the $B (note how in simple camel case, where each capital letter is on its own, "one step before the downtick" is always the same as uptick).

This is a trickier condition to handle because we need to look one extra step ahead in the string, while there are no built-in enumerators doing that. There is also a pesky special case of the last two characters, for which there is no character two steps ahead. The simplest solution is to set up a stream to feed us those one extra step ahead characters:

splitCamelCase
    | future |
    future := ReadStream on: (self copyWith: $Z).
    future skip: 2.
    ^self piecesCutWhere: [:each :next |
        each isLowercase & next isUppercase
        | (next isUppercase & future next isLowercase)]

Note how the extra $Z tacked onto the end of the original string takes care of the special case of the last pair of characters (any other uppercase character would do, of course).

This works, and is pretty simple, but doesn't quite feel like a one-liner. Here is an elegant twist we can do to improve it. In the first solution we needed the 'future' stream to see the character two steps to the right of the potential cutting point. If we reverse the order of processing the string and go right to left, we will already have seen that character by the time it becomes the one two steps to the right from where we are--so all we need to do is remember if it was uppercase or not.

Of course, we cannot change #piecesCutWhere: to go from right to left, but we can reverse the string before traversing it, and then reverse the results we get back. With a bit of creative rewriting of the split condition, this gives:

splitCamelCase2
    | lastWasUppercase |
    lastWasUppercase := true.
    ^(self reverse piecesCutWhere: 
        [:each :next |
        lastWasUppercase not | next isLowercase & (lastWasUppercase := each isUppercase)])
            reverse collect: [:each | each reverse]

This was the last solution I left as a comment in Travis' blog. Now the main part of this post. I wouldn't be myself if at this point I didn't write a continuation-passing style mindbender to do the job.

splitCamelCaseCPS
    ^(self
        inject: [:next :next2 :return | return value: Array new value: String new]
        into:
            [:k :each |
            [:next :next2 :return |
            k
                value: each
                value: next
                value:
                    ((each isLowercase & next isUppercase or: [next isUppercase & next2 isLowercase])
                        ifTrue: [[:words :thisWord | return value: (words copyWith: (thisWord copyWith: each)) value: String new]]
                        ifFalse: [[:words :thisWord | return value: words value: (thisWord copyWith: each)]])]])
        value: self last
        value: self last
        value: [:words :thisWord | words copyWith: thisWord]

This sure looks like write-only code, but not for the same reason lots of Perl code does. There is a very clean structure behind this one, but it's hard to see it because the essential pieces of functionality are hidden behind anonymous blocks and their interaction through the generic #value:... messages. And also because this isn't the usual style we do things in Smalltalk.

We are going to take it apart piece by piece, and then put it back together in a few possible variations to explore how it works.

But first about continuation-passing style. Continuations are not only those special language constructs that exist in Scheme for torturing computer science students, or that can be added to Smalltalk to implement cool web application frameworks. In a more general sense, continuations are found in any program. A continuation is a function that represents the future of computation. For example, if we consider the expression

    3 + 4 factorial

while the factorial is being computed, the future is to take the result and add it to 3. This can be written in Smalltalk notation as a block

    [:result | 3 + result]

and we can think of returning from #factorial as invoking that one-argument block with the result we are returning as the argument. This one-argument block is a continuation. At any point where we can imagine stopping the execution of any program in any language, there is one waiting for the result. At least in theory.

Theory is great, but what use is it? One thing, it presents a nice model of multiple-value returns. Returning more than one value from a function may seem a weird thing, but it is not at all if you think of that return as calling a continuation. From that viewpoint, the continuation at a multiple-value call site is a function of several arguments, and returning multiple values simply means calling that function and passing it those arguments.

If that still sounds too far from practical use, have a look at VisualWorks method Win32DialogSupplier>>parseDefault:into:ifGarbage:. The method parses a single file name string into the path and filename components, but instead of returning them as a two-element array or something of that sort, it calls a block it expects as an argument, passing them as the arguments. That block is a continuation representing a multiple-value return!

This is an example of continuation-passing style. In general, CPS is when closures are used to implement or reimplement some features of execution semantics. These features can be something not available in the base language, such as multiple-value returns. Or they can be features already available, but reimplemented in CPS to gain more control over execution, or to work around implementation limits. For example, algorithms that require deep recursion can be implemented using CPS in environments with limited stack depth--though we are not going there in this post...

Note that CPS is not directly tied to continuations in the Scheme or Seaside sense. They are certainly the same theoretical artifacts, but you don't need continuations in a language to program in CPS. Scheme continuations are reified continuations of the base language, while in CPS we essentially construct a new language by explicitly managing its continuations. Moreover, it may come as a surprise that CPS in Smalltalk is much more ubiquitous than most people think! For one example, just look at #ifTrue:ifFalse: message. What is it if not two continuations passed to the receiver, the receiver deciding which one to invoke?

With this in mind, let's get back to our string splitting. Here is the basic idea we are going to exploit. The first two solutions based on #piecesCutWhere: were essentially the same. There was a block evaluated for each potential split location. The block examined three characters: the one before the split point, the one after, and the one following that. The only difference was the way of supplying the block with that third character--and together with that, the direction of traversing the string.

In this last solution, we follow a similar approach by setting up a structure of closures such that for each position in the original string there is a closure holding onto the character at that position, and supplied with the two characters that follow from other closures. This is the first piece of the puzzle, set up by inject:into::

    self
        inject: terminatorBlock
        into: [:previousK :each | [:next :next2 | ...previousK value: each value: next... ]]

It's not every day that we write #inject:into: with a block as the first argument, and the entire body of the second argument being a block as well, so let's look closely what happens here. For each position in the string, we create a closure with two arguments, :next and :next2, closed over a similar closure created for the previous string position (:previousK) and the current character (:each). For the very first character in the string, :previousK is the injected terminatorBlock. The result of the entire #inject:into is the closure created for the last character in the string.

We are going to call the closures on this chain the "work closures", since they are the backbone of the entire algorithm. (Perhaps calling them "vertebrae" could be a good idea too). We can get the algorithm going by calling the last closure. The closure code (so far unspecified, except that it has to do "previousK value: each value: next" at some point) would in turn invoke the closure for the second last character it holds onto as :previousK, passing :each as the first argument to it. That closure would invoke its own :previousK, and so on.

All that's left is coming up with the code inside the blocks to collect the characters into a sequence of words, grouping words according to our usual split condition.

Of the simplest solutions, there are two dual ones. We can accumulate characters in an OrderedCollection of OrderedCollections. The outer collection is the resulting sequence of words, and the inner collections--the individual words. Two possible solutions based on this idea correspond to two directions of building the result: we can add the current character to the result before calling previousK, of after. In the former case we are accumulating the result in the reverse relative to the string order, as we are going deeper and deeper into the nested invocations of closures in our closure chain (which goes from the end to of string to the beginning). In the latter we are doing it as we are returning from the nested invocations, so the characters are added in the same order they appear in the string. For the forward order, the main traversal block would be:

    [:next :next2 | | words |
    words := previousK value: each value: next.
    words last add: each.
    (each isLowercase & next isUppercase or: [next isUppercase & next2 isLowercase])
        ifTrue: [words add: OrderedCollection new].
    words]

It first asks the previous closure to give it the words so far (which are the words from the beginning and up to the current position in the string), appends the current character to the end of the last word, and then if the string should be split after the current character, adds a new word to the result. The terminatorBlock at the end of the main closure chain should provided the closure for the first character with the empty result:

   [:next :next2 | OrderedCollection with: OrderedCollection new]

Putting it all together (and preserving the color-coding of the above pieces for clarity), we get

splitCamelCaseCPSForward
    ^((self
        inject: [:next :next2 | OrderedCollection with: OrderedCollection new]
        into:
            [:k :each |
            [:next :next2 | | words |
            words := k value: each value: next.
            words last add: each.
            (each isLowercase & next isUppercase or: [next isUppercase & next2 isLowercase])
                ifTrue: [words add: OrderedCollection new].
            words]])
        value: self last
        value: self last)
                collect: [:each | String withAll: each]

The last pieces that bind it all together are the invocation of the closure chain that passes the last character of the string as :next and :next2 to the closure for the last character. This is similar to passing $Z in the first two solutions. This allows us to use the simplest form of the split condition, without recognizing the last two characters as a special case. The characters we pass here don't matter since they are not included in the result. They only have to be of the same case as the last character, to not trigger a split condition--so simply using the last character works fine. Also, there is an outer #collect: to convert OrderedCollections in the result into proper Strings.

The 'reverse" solution is similar:

splitCamelCaseCPSReverse
    ^((self
        inject: [:next :next2 :words | words]
        into:
            [:k :each |
            [:next :next2 :words |
            (each isLowercase & next isUppercase or: [next isUppercase & next2 isLowercase])
                ifTrue: [words addFirst: OrderedCollection new].
            words first addFirst: each.
            k value: each value: next value: words]])
        value: self last
        value: self last
        value: (OrderedCollection with: OrderedCollection new))
            collect: [:each | String withAll: each]

The interesting difference here, highlighted in blue, is that all the work closures get a third argument, for the words collected so far, and the initial empty result is created when they are first invoked, while all the terminator block does is turning around and sending back the collected words it received as the final result. And, of course, characters and words are now tacked onto the result from the front, since we are traversing the string from the end.

Now we are prepared to look at the original #camelCaseCPS. It's more involved than the two solutions above, especially the "forward" one, because of a few extra restrictions I introduced to give it more of the "single breath" or "one-liner" spirit. OrderedCollection is a fine Smalltalk tool, but the solution will have a more austere functional feel if we stick to the simple Arrays and Strings, cons up new ones instead of mutating the original, and use the simple #copyWith: to build the result.

Since #copyWith: appends an element to a collection, this latter restriction immediately forces us to build the result in the "forward" style, if we want to avoid explicitly reversing things afterwards. The no-mutation restriction encourages us to split into two parts the single result that gets passed around: "complete words so far" and "current word so far," to make them easier to manipulate.

And here we have a problem. In our "forward" solution, the result so far was returned from the invocation of the previous closure. The problem now is that the result is split into two parts, and we need to return two values instead of one. Instead of going the ugly route of returning an array of values, let's remember what was said about multiple return values--that theoretically, they are nothing but multiple-argument continuations. So, instead of using the built-in single-value return, let's construct a multiple-value return using CPS. Our work closure becomes:

    [:next :next2 :return |
    previousK
        value: each
        value: next
        value:
            ((each isLowercase & next isUppercase or: [next isUppercase & next2 isLowercase])
                ifTrue: [[:words :thisWord | return value: (words copyWith: (thisWord copyWith: each)) value: String new]]
                ifFalse: [[:words :thisWord | return value: words value: (thisWord copyWith: each)]])]

The new :return argument is the return continuation, passed by the caller as the third argument of the #value:value:value: message send in this same block. It takes two arguments: :words and :thisWord, containing the words and characters of the current word collected so far, appends the current character at the proper place, and passes the new words and thisWord collections to the :return block of the current closure--that it, returns these two values to the sender.

That third argument could also have been

    [:words :thisWord |
    (each isLowercase & next isUppercase or: [next isUppercase & next2 isLowercase])
        ifTrue: [return value: (words copyWith: (thisWord copyWith: each)) value: String new]
        ifFalse: [return value: words value: (thisWord copyWith: each)]]

without changing the end result. The only difference is the moment when we choose what execution branch to take. The latter version is less interesting--we always pass the same block, and choose how to build onto the result when the block is invoked. But we can make this decision before the block invocation, since all information it relies on is already available at the time of the #value:value:value: send--so the original proposed block does the test as soon as possible, and passes the chosen execution branch to previousK already pre-determined.

The terminator block becomes

    [:next :next2 :return | return value: Array new value: String new]

which is the same thing we had in #camelCaseCPSForward, except that now we are using our home-brew continuation-passing multiple-value return instead of the built-in Smalltalk block return.

One last wrinkle is the very first of the continuations that build the result--the one passed as the last argument of the outermost #value:value:value: message, and invoked by the last work closure. It is a block that takes all the words collected so far and the last current word, and joins them together.

Putting it all together, we get the original #camelCaseCPS.

And one last thing (sorry, could not resist): do not try this in Java!
Inner classes notwithstanding.

Just Smalltalk

Sets and nils

August 13, 2004 20:43:28.670

Travis is implementing Sets-with-nils based on an object other than nil used to mark empty slots. Of course, there is a dual solution: leave nil as an empty slot marker and use a special object to store in place of nil. Today being Friday and next VW build coming next week, I guess I don't really need this image...

This approach is much simpler. In Set:

  1. Add a class variable with a "nil marker" object--an instance of Object will do.
  2. Change findElementOrNil: to replace nils with nil marker before searching.
  3. Remove special check for nil from add: and store the nil proxy instead of nil; ditto for noCheckAdd:.
  4. Change do: and collect: to pass nil to the block when the element they see is a nil marker.

That does it for Sets. For Dictionaries, the story is even simpler: just remove the checks for a nil key--since we are storing associations, there is never a question whether an element is a real nil or not. IdentityDictionary story is more involved, but boring: since it reverts back to a "naked" key scheme, it needs treatment similar to Set to convert nils to and from the nil marker in associationsDo:, at:ifAbsent:, at:put:, findKeyOrNil:, keyAtValue:ifAbsent:, keysAndValuesDo:, noCheckAt:put:.

No system mutations are necessary with this approach.

The code is here. Note that this is a dangerous change--not even because there could be something I missed, but because there could be places in the image that rely on nils being filtered, which would create subtle bugs.

Just Smalltalk

Pragmatic Pragmas

July 7, 2004 2:47:09.106

In his blog, Eric gave a few interesting examples of using pragmas. All such applications eventually need to find methods marked with pragmas and analyze them. An obvious solution, as shown in those examples, is to iterate over all methods of a class looking for methods with the annotations we are interested in.

An additional complication not shown in Eric's example is required to account for subclasses. For example, if we use pragmas to label methods in class A, and the code searching for those methods searches only that class, methods in a subclass of A marked with the same pragmas will not be found unless the subclass also overrides the search logic. The proper solution that would not break in this situation should search all classes from the actual receiver class and up to the one that implements the search.

The resulting solution is rather complicated, featuring several nested loops. Because any application that uses pragmas eventually needs to search for them, VisualWorks (as of version 7.1) includes a class that makes it easier.

The class is called Pragma and is located in the Tools namespace. The class includes a number of methods that search classes for pragmas with particular keywords, for example

	Pragmas allNamed: #assert:with: from: Class1 to: Class2

That expression returns a collection of Pragma instances representing the result of the search. Each element represents one pragma--one expression in angle brackets--so for the following method:

    Test>>lessThanZero: aNumber 
        <assert: true with: -1>
        <assert: false with: 2.3>
        <assert: false with: 0>
        ^aNumber < 0

the result will include three instances of Pragma, one for each <assert:...> expression in the code.

In its role in the system, Pragma resembles the Message class. Just like Messages are only used by Smalltalk code that needs to reason about messages, and not by the execution machinery during routine message dispatch, Pragmas are throwaway objects representing the result of a particular search for methods marked with pragmas, and are not used to actually store pragma information inside CompiledMethods.

You can send messages to an instance of Pragma to get information about the method it is found in, and about its own arguments. For the above example, here is what you can ask a Pragma representing the first "<assert: true with: -1>", and what it would answer. The first three messages tell something about the method the pragma is in, the others--about the pragma itself:

  • method => a CompiledMethod (Test>>lessThanZero:)
  • methodClass => Test
  • selector => #lessThanZero:
  • message => a Message with selector #assert:with: and arguments #(true -1)
  • keyword => #assert:with:
  • arguments => #(true -1)
  • argumentAt: 1 => true
  • numArgs => 2

Instances also understand the message #withArgumentsDo:, which allows to refer to pragma arguments by names rather than by positions. Here is a complete example of iterating over #assert:with: pragmas in a particular class:

	"In an instance method of Test."
	(Pragma allNamed: #assert:with: from: self class to: Test) do:
		[:pragma |
		pragma withArgumentsDo:
			[:result :argument |
			(self perform: pragma selector with: argument) = result
				ifTrue: ...

Just Smalltalk

Too revolutionary?

March 11, 2004 23:44:41.305

Watching an old interview with Man Ray, something struck me as awfully appropriate in the context of the recent exchange in Ian Bicking's and Jim Robertson's blogs, with Ian complaining that Smalltalk is, essentially, too revolutionary. In the interview, Man Ray said (the quote is approximate), "They say that I am ahead of the times, still. No, I am in my time. It's those who say it who are behind the times." Simply superb.

Just Smalltalk

Governing body--what about a head?

February 1, 2004 17:05:52.907

Posts and comments by Rich Demers and Michael Lucas-Smith pondering a governing body that would help the community "further evolve" Smalltalk made me remember the last paragraph of this post from Jim Robertson's blog, which I emphatically agree with:

I've worked in large groups, and one thing I've noticed is that the collective wisdom goes down as you add people to the group. In a group setting, people will agree to any number of things which they would never agree to individually. We've all seen that in meetings - I have little faith in emergent wisdom happening in a large group setting. In fact, based on the effects of propaganda (both in the political and technical realms) in the past, I'm much more comfortable in predicting emergent nonsense.

Here is how it goes when I think about a "Smalltalk evolution committee". My first though is "great, now we can sieve through the ideas out there and choose the best to follow up on, and decide how to move ahead". My next thought is an image of even a mildly controversial idea offered to such a committee. If as trivial an issue as ifNil: can spread discussion like wildfire through three blogs, imagine proposing something like pragmas or namespaces, and the resulting flurry of emails recounting the author's philosophical stances and convictions. That's where I start suspecting that such a committee would not actually get that much accomplished.

At least if it is understood as the vehicle of language evolution itself, which isn't the only possibility.

Let's look at the ANSI Smalltalk committee. X3J20 is certainly a good thing to have, even though many would argue it does not specify quite enough. But, that committee hasn't invented Smalltalk or any of the language features described. They summarized and reconciliated what has been created and tested in practice by different groups. The most prominent ivention of the committee itself is probably the SIF format, and how many SIF fileouts do you have on your system at the moment?

The useful thing that they did was not evolving the language, it was capturing the evolved features that were worth capturing. This is what committees are good for: recording and reconciliating--not inventing.

There is nothing new or surprising about it. Evolution cannot be governed, it just happens. A language supervision body would be a good thing, as long as its a tool for recording and refining as a recommendation what has already been created, tried, and won enough of a following to make it worth considering--leaving to the community the right for "unsupervised" language extensions, as the method of the evolution itself.

Just Smalltalk

ifNil: isOK ifTrue:

January 31, 2004 13:07:50.406

Quotation time. In his comment to another post, Rich Demers made an interesting point about ifNil:

Interesting. What the #ifNil: method does with what is returned by a block is, of course, up to the method - which means there is no syntactic means for a reader to know what will happen unless he is familiar with the method -- in other words, no linguistic redundancy to rely on - which adds costs of learning how to read the language -- a cost trade-off I guess.

Obviously I'm too late, but I would have argued in favor of #ifNil: working exactly the same as #ifTrue: -- returning a Boolean -- in favor of readers over writers. Language design is more than just writer convenience.

further adding

In my last comment (above), I argued that, as a controlling method and from the p.o.v. of code readers, #ifNil: should work exactly the same as #ifTrue: -- that it should return a Boolean. I should have said return nil if the receiver is notNil.

I find it interesting because the same point was argued when Eliot and I worked on adding ifNil: to the base. Quoting from Mars, our action request tracking system:

Also, the correct return value for ifNil: when the receiver is not nil, and for ifNotNil: when the reciver is nil, should surely be nil for symmetry with ifTrue:.

The very first ifNil: that was included in the base image was indeed returning nil for non-nil branches. I changed it to what it is now with the following argument:

The idiom for ifNil: use is something like

	^self foo ifNil: [self defaultFooValue]

The analogy with ifTrue: is misleading. ifNil: is not (just) a single-branch control statement. As a function, it is a "nil filter". It has to pass non-nils through, otherwise it--well--has no value :).

It is also a portability issue. Everyone defines ifNil: to return the receiver.

That's a rather brief reasoning, so I'll elaborate starting from the end.

We were not the first--in fact, possibly the last--Smalltalk implementation to add ifNil: to the base image. Everyone indeed defines ifNil: to return the receiver in non-nil cases. That alone is a reason enough to go with the receiver returning approach. But, there is more to be learned if we consider why that became the de facto standard.

The key is in its common usage pattern: value ifNil: [defaultValue]. For me, this is strongly reminiscent of one of the long existing idiomatic uses of or in Lisp. Because in Lisp nil is also the falsity value, and any other object is considered a truth value, (or value default-value) means not only the usual "short-curcuited" Boolean OR: "if value is true, the result is true, otherwise the result is the same as default-value", but also "if value is not nil, the result is that value, otherwise it is is the same as default-value". So, the precedent really goes quite a ways back.

Here is how this common pattern is the key to why returning the receiver is good. It illustrates that ifNil: is most often used for value. Therefore, it is not as much a control statement as it is a function that chooses values.

If that is still not compelling enough, take this tack. ifTrue: may be coming to mind as an analogue of ifNil: first of all because they are both "if" messages, and they have a similar structure of their complementaries: ifNotNil: is like ifFalse:, and so on.

But consider other "if" messages: at:ifAbsent:, detect:ifNone:. Nobody would argue that at:ifAbsent: needs to answer nil when the element at a given key is present. Stop and think about it for a moment. It only sounds ludicrous because we are used to at:ifAbsent: as it is. It certanly would be possible to reason: "at:ifAbsent: is a control structure that takes the ifAbsent: branch when the key is not in the collection, therefore for symmetry with ifTrue: it should answer nil when the key is in the collection".

So we are back to the same conclusion. The appropriate "if" to think about when looking at ifNil: is at:ifAbsent:, not ifTrue:. They are both more like expressions that select values, than control statements.