Introduction
Smalltalk is remarkable for its expressiveness and malleability. While the various dialects share these strengths, they have unique strengths and weaknesses in performance, portability, and cost. A universal GUI framework would be nice to have, but here we focus on domain code, and streams in particular. How can one expect to move code between dialects when something so basic as a ReadStream behaves differently on the different dialects? If one values such portability, there are two solutions: create and use portable messages; change the dialects to consistent semantics.
What's in a Name?
I originally learned about Smalltalk through trade journals and, with my interest piqued, a book or two. Some inquires quickly revealed that I had neither hardware adequate to run the existing systems, nor enough money to obtain one. An educational discount on Smalltalk V/DOS made Smalltalk affordable to me. After several failed attempts at solving simple problems, I got V/DOS to do what I wanted, and noted that Smalltalk V/Win was also available at a deep discount. After some more work, I found the early Dolphin betas, and never looked back.
Dolphin forced me to make changes, but offered a lot in exchange for the trouble. Also, as a beginner, I did not have very much code to be adversely affected. Zoom forward in time, and I have a lot of code to protect, not from Dolphin, but from the operating system to which it is tied.
Many others will have similar stories. Having seen the current state of Windows (which I consider to be dismal) coming for some time, I have long investigated where I might go next, only to discover various deficiencies in the dialects. Despite its various problems, Squeak was difficult to ignore, and Dolphin continued to serve well enough to allow me to continue with it.
During my years with Dolphin, I became very fond of programming with streams. This was partly because I dealt with many streamed data sources (e.g. sockets, serial ports), and because relative addressing of indexable collections can be very powerful.
In searching for a portable Smalltalk dialect, I quickly began tinkering with streams, only to discover that #next did something that struck me as horribly misleading: it answered nil on stream exhaustion! Having learned to appreciate Dolphin's use of EndOfStream to alert me to a potential logic error, I was very concerned about the errors that might go unnoticed. Tinker some more, and #next: has the same problem (truncates silently) on Squeak, and raises something strange on VW.
Dolphin gets it right. The programmer is free to choose between #nextAvailable: (authorizing truncation) and #next:, which raises EndOfStream on exhaustion. For benefit of some Squeak code that I ported to Dolphin, I added #nextOrNil to match the Squeak behavior, freeing #next to signal the same error as other methods on exhaustion.
I submit that Dolphin streams behave as advertised, and should be emulated on other dialects. Failing that, the poorly-behaved methods should be deprecated and replaced with #nextOne, #nextMany:, #nextAvailable:, and #nextOrNil, the first two signalling EndOfStreamError. Using a default action to defeat the error defeats the purpose of raising errors and should not be considered.
How Can This Be Done?
So you consider this to be a radical proposal? I largely disagree. One could proceed as follows:
- Using the current Pharo image, rename next to nextOrNil, and next: to nextAvailable: (that is afterall what they do)
- Add next and next: that raise EndOfStreamError.
- Note that all code in the image works as before.
- Code not in the image during the renames can be prepared in a throw-away image by repeating the first step, filing out the code, and filing it into a converted Pharo image.
If #next and #next: were not so descriptive of their intended (if not actual) purpose, I would not lobby for a breaking change. However, they are the natural choices, so I think Cincom should enact such a change across a couple of versions (initially deprecating #next in favor of #nextOrNil) and then converging on mutually agreeable semantics. Pharo, having no legacy, should do this from the beginning.
An Alternative
Should this proposal not be adopted, I will on my own (you are welcome to join me)
- Deprecate #next and next:
- Add nextOrNil, nextAvailable:, nextOne, and nextMany:, raising EndOfStreamError as appropriate
I've been thinking about this issue. See http://bugs.squeak.org/view.php?id=6755 and animated discussion in squeak-dev. Main two problems are: 1) code base to be changed is huge 2) next and next: are ANSI I guess
That explains the VW choice to raise a notification with a default action rather than an exception (mantis 6755 is similar): upward compatibility... But I agree, this is bad, Exception is far better.
Now I think that the action upon endOfStream should be programmable. One solution is to have an endOfStreamAction instVar like this: Stream>>next
Since nil value = nil, current Squeak behaviour would be to let endOfStreamAction unitialized and this would have no impact on performances (review Andreas Raab arguments). And for those preferring an Exception, simply feed with a block: Stream>>letSignalEndOfStream
All responsibility would be in hands of programmer. What do you think?
Arghh, I hate wiki syntax messing my smalltalk syntax...
The endOfStreamAction: above is simply a block signalling EndOfStreamError?