On Learning Smalltalk
blog.robsayers.com
blog.robsayers.com
The image—all your code, data/state, your entire OS and windowing system, your IDE, all rolled up into one—that was an interesting approach but honestly that is the worst thing about Smalltalk. No modularity. No version control. Your image persists your data and changes forever—including any mistakes you make, knocking around inside big ball of ever-declining clarity and reliability. Over time every image collects entropy, mistakes, and bitrot like no one's business! Eventually it gets to be impossible to figure out how to fix it, or takes so much energy to maintain you won't bother. Then you have to chuck it and start afresh. Rinse and repeat! Give me today's alternative, non-persistent dev environments and version control, any day.
Seems like it makes operational tasks, and really anything that involves distributing the code to anyone who is not the developer, is more difficult than with non-image based languages.
And we can run the Lisp program and not touch it. But… if we want, we can easily connect to the running Lisp image, introspect it, change a couple parameters… or do a software upgrade, which can involve installing Lisp libraries… and we could connect to the image on the remote server from our editor and develop the app (while still saving the changes in source control locally), but that would be silly and it isn't a standard way.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
./bin/visual nbody.vw_run.im -nogui -pcl MatriX -filein nbody.vw -doit 'ObjectMemory snapshotThenQuit'
...
...
./bin/visual nbody.vw_run.im -nogui -evaluate "BenchmarksGame do: 50000000"
...
...
-0.169075164
-0.169059907The “doesn’t play well with others” problem of Smalltalk was kind of a killer.
https://news.ycombinator.com/item?id=29223880
> … storing source code inside a memory image didn’t help with interoperability…
"Interoperability" covers a lot of things, was there something particular you had in mind?
----
> … its own OS and GUI.
Well there are examples of bare metal Smalltalk (I'm guessing we could say the same of Java?)
https://github.com/michaelengel/crosstalk
but that was kind-of unusual and afaik commercial Smalltalk implementations targeted specific host OSes.----
Similarly back in the '80s commercial Smalltalk implementations provided their own GUI L&F (and in the '90s Java provided their CrossPlatformLookAndFeel aka Metal).
When OS GUIs became available in the '80s, Smalltalk implementations either targeted specific host OS GUIs (so on MS Win use MS Win controls) or provided portable GUIs based on emulated L&Fs (OS/2, Motif, MacOS, MSWin) (and in the '90s Java provided AWT/Swing…).
IBM Smalltalk (Visual Age) GUIs were based on what would become in the '90s Java SWT.
----
So no, not really "its own OS and GUI" — when GUIs became provided by the host OS, Smalltalk implementations provided ways to use the platform L&F.
Here's some "play well with others” "interoperability" —
Sept 21, 1987 — "A version of the Smalltalk-80 object-oriented development environment … has been announced by Parcplace Systems.
Smalltalk-80 DE version 2.2 is said to provide Unix environments with interprocess communication via sockets and access to Unix C shells. On the Apple Macintosh, it provides access to Desk Accessories and provides the ability to cut and paste between the Smalltalk-80 system and the clipboard."
https://books.google.com/books?id=ldk7z4Q-WWYC&pg=RA4-PA4&lp...
For sure! Is it a well-founded complaint or just an obvious difference?
What if the Smalltalk image is a cache not an archive?
What if source code is exported from the image as text files and archived, and each week the source code is loaded into a base image for test?
That's an interesting perspective, thank you for sharing.
Smalltalk was trying hard to be different, and there were a lot of neat ideas in there, but while it seems like a very powerful concept to have your OS/editor and language all rolled up into one, that doesn't mean it's a good idea. Kind of like LISP macros. Seems like such a powerful and useful concept, but on the other hand, if you give programmers too much power, they are guaranteed to misuse it.
One person's perspective; here's another:
Maybe. But not predictably. It will have delays, they may be small, but there is know way to know when they will come. This can be managed but it is all overhead. Manual memory management has none of that run-time overhead (but a tonne of programmer overhead)
I agree that manual memory management is much more predictable than garbage collection, I just wanted to touch on the nuances of the memory overhead of manual memory management.
Also, if the program creates lots of short-lived objects (which is often the case), a generational garbage collector would be pretty fast because it will reclaim/move a few objects rather than deallocating many objects. An arena collector or allocating on the stack might be better (and manual methods) in this scenario though.
It was my understanding that C4 doesn't do pauses.
But what is it? Where can I find out about it? A deterministic garbage collector would (a) prove me wrong and (b) be a Very Good Thing
This is why containers became so successful, IMO. For a long time I thought being able to log in to a server and set up a new cron job to deal with some maintenance task was a strength. It turns out that over time it does become a problem.
https://www.google.com/books/edition/Mastering_ENVY_Develope...
Now I regard it as the greatest version control tool I have ever used. (It is also a package manager as well as a build tool). Version control at the method level!
Such that I'm curious if moving models around for use would have been easier in some image based languages.
It's simply anachronistic to judge Smalltalk implementations of that past decade against version control of this decade, when there have been newer Smalltalk implementations with version control.
Even back in 1983 the blue book discussed "Storing the set of Changes on a File" (page 465) and "Commands that check for conflicts" ( http://rmod-files.lille.inria.fr/FreeBooks/TheInteractivePro... page 472).
"Whether your development team consists of one person, or many: Do understand and use the change manager. The change manager is one of the most important tools for software development in the Smalltalk-80 environment. … It allows you to selectively browse [changes], remove [changes] incorporate them into another version of the system, check for conflicts, and prepare the changes for release to other members of the development team or to end users." (page 500 "Smalltalk-80 Software Development Do's and Dont's")
> Your image persists your data and changes forever—including any mistakes you make…
Even back in 1988, source code changes made with the Smalltalk IDE were automatically logged to a text file (the change log) and we could choose which of the logged source code changes to apply later to an unchanged image file.
So no, mistakes were not forever.
All of it was pretty useless compared to using Envy/Developer which was a unique version control system that was based entirely around Smalltalk and collected up your changes in a way that made sense for sharing.
The guys at ParcPlace never seemed to quite understand what we were facing back then - your average departmental programmer trying to replace a green screen in a team of 20 other programmers not only had to cope with a complete change of paradigm for the programming language, but the version control systems were non-existent or useless even compared with simple systems like SCCS. Change logs just didn't cut it when you got past 2 developers.
The vanilla VisualWorks simply didn't scale to multiple users trying to share code. Envy/Developer fixed that (and how! Still the best version control system I have ever used).
The "forever" thing might be an exaggeration, but in practical terms I had a lot of guys destroy images accidentally by over-ridding something in Object or whatever while they were learning and causing havoc for themselves. Trying to piece together their good work from the change log was...effectively impossible until they learned what they were doing due to the total scattergun approach a lot of them used. So perhaps not "forever", but given the time constraints in a normal development environment, it was forever for certain values of forever.
You tell us the people didn't know what they were doing — so why blame the tools?
(Incidentally, I used Envy/Developer for years.)
Not every programmer is a superstar. Visual basic was a much better environment for average guys doing green screen replacement jobs.
I was briefly involved with a project that sounds a bit like that — a 2 week training course for current staff, then rewrite everything from data model to U/X in half-the-usual time because someone said OO is more productive.
That project used ENVY/Developer. The staff admin deleted the repo. The staff wouldn't have passed phone screen for Smalltalk work (maybe some would have in 6 or 12 months). When the people don't know the basic tools, that's what matters.
Just curious, what process did you tell them to get past their "scattergun approach"?
I never looked at the 1984 "Smalltalk-80 The Interactive Programming Environment" book until about a year ago, so very much with the benefit of hindsight —
"At the outset of a project involving two or more programmers: Do assign a member of the team to be the version manager. … The responsibilities of the version manager consist of collecting and cataloging code files submitted by all members of the team, periodically building a new system image incorporating all submitted code files, and releasing the image for use by the team. The version manager stores the current release and all code files for that release in a central place, allowing team members read access, and disallowing write access for anyone except the version manager." (page 500)
http://rmod-files.lille.inria.fr/FreeBooks/TheInteractivePro...
Envy fixed that, you could start with a fresh envy image and grab your updates. You could also isolate developers in their own branch like git and get proper warnings when you had integration issues when merging.
Basically the integration job went from full time to a couple of hours a week.
> … filing that into other developers images as they went…
Seems to me that after "building a new system image" all their developers worked with that same fresh system image (built from known archived sources).
(Perhaps I'm wrong about that.)
Then we settled on pre built images with just the across team shared code. But that got difficult too, as some teams didn't want to upgrade as quickly as others on the shared code base, so we were maintaining several versions of that.
The teams themselves had regular image consolidations saved, plus file outs and some PVCS for those, but it got increasingly harder to make a consistent merged image no matter what and devs were spinning their wheels in a kind of code merge hell at the method level that is unique to smalltalk.
Then we got envy and it fixed everything.
ParcPlace did eventually come up with a different structure for packaging and sharing code but it was too late and too simplistic to be useful.
Puzzled. ENVY/Developer is still Smalltalk and code merge still method level?
Perhaps what was shared within teams and between teams changed?
(With the benefit of hindsight) seems to me that many teams made their mistakes with vanilla Smalltalk and avoided those same mistakes when they moved to ENVY/Developer.
Smalltalk always felt like it was from the future to me. Its vision and respect for human thought and creativity is so grand it makes me blush. It represents a real belief in technology and what people might do with it. It's full of hope and humanity. It's basically the Shire of programming environments -- if the Shire were part of the Standford campus.
If you haven't checked Smalltalk out, you should free your mind and give it a shot.
The idea of a bundled IDE is also something I am not completely convinced that it is the best solution. On the one hand, it allows powerful operations, but on the other hand, every feature has to be developed explicitly for that language.
Instead, I prefer the modularity of Unix tools which can be used to develop tools by combining multiple technologies.
"Like Docker, but for Smalltalk images."
Overall, I'm glad I learned it, much like TCL and Lisp, but I'm definitely glad I don't use it professionally.
The languages basically take their premise to the extreme, where the premise for each is "What if everything, including the language itself, was ..."
TCL: ...a string?
Lisp: ...a list?
Smalltalk: ...an object?
I don't know enough about it, but I think with Haskell the suffix would be "...a function?"
Neat experiments, neat learning them, but I appreciate other languages more for actually implementing large projects.
"What if everything was immutable?"
Which I guess doesn't really include the language itself.
But it seems more fundamental to Haskell than first class functions, which exist in many other popular languages.
You're essentially building up a catalogue of messages and answers and so it's not particularly ergonomic to jump through several layers of abstraction in the browser to get what you need. And there's no arbitrary limitation on your code such that a class can only have 10 methods in it, or 100 lines, or some other silly thing that forces you into creating a more convoluted architecture with more indirection.
I've grown weary of OOP for a long time because of how hard it is to find an effective application of it that doesn't grow into a collection of explicit design patterns with some business logic buried in the depths of the hierarchy. My brief experience with Smalltalk essentially revived my interest in it and encouraged me to be more thoughtful about my approach without throwing it all out and pining for a purer, more functional language that trades OOP's big problems with a set of its own.
* Interfaces
* Messages
* Some combining of data and functions
* Some polymorphism
Not so good:
* Inheritance
* Open/close
* Most Encapsulation
* Most everything else ;)
I've never heard of such a restriction, even in very early programming languages. What language are you thinking of where you're limited to a specific number of methods or lines per class?
I loved working with Smalltalk as a language, and developing inside the fully integrated VM. It's my fondest development experience to date.
I really didn't love deploying Smalltalk applications and even collaborating was more difficult than file based languages/systems. Deployments (at the time) were a practice of shipping the image to the servers that needed to run. There literally is no (separate) "runtime" it's all in the image. This thing got bulky real quick.
I haven't touched Smalltalk in a while, so maybe (hopefully?) things have improved and deployments are no longer a problem.
As far as working with the code, debugging, reading and writing were concerned, it was great. It really gave me a taste for thoughtful OOP, simple documentation, and even TDD-style development again, because the whole environment just worked perfectly for it in a way that would be hard to reproduce in a traditional language where you're organising things on the file system.
Dealing with version control was a little more awkward (getting Git in place), and I'm glad I didn't have to worry about deployment or any kind of maintenance. Possibly GNU Smalltalk would be an ideal middle-ground there.
It's also one of the few occasions where I've not really had to rely on the internet so much to get help when I need it. It's been enough to have a book on Smalltalk and to rely on the documentation in the IDE.
I fail to see how shipping a tree shaked version of the image was any different than shipping a C++ application with MVSCRT.dll, for example.
As for source control, while it was indeed a problem with workarounds like text export/import, image based SCM were eventually developed.
Deployments can start with a stock image, load your code and run, without you having to move around an actual binary blob from machine to machine.
On one hand I like the idea of an image, being inspectable, adjustable, etc. Yet on the other hand I'm also a fan of stateless, pure-functional approaches where possible (e.g. Haskell, Nix, etc.).
I get a similar feeling from Emacs: it's really useful to inspect, override, debug, etc. the underlying Lisp; yet I prefer to keep my config as declarative as possible (e.g. via use-package). In fact, I rebooted recently and found that ANSI colour codes had stopped working in my Emacs terminals: turns out I'd forgotten to update my xterm-color config when switching from shell-mode to shx-mode several weeks ago!
https://stefan-marr.de/renaissance/
https://github.com/smarr/RoarVM
This was a research project so performance on a single core was poor compared to the official Squeak virtual machine, but it was an interesting exploration of the natural fit between objects/message passing and multiple cores.
It is indeed a natural fit to many core architectures. I frequently mused about giving every message process its own thread. I’ll read the papers you mentioned.
But for anyone saying they learned Smalltalk in the Elder Days (1980s):
No, you didn't.
In [1] I discuss learning it in 1978, and there was an icon prototype written in Smalltalk that same year.
It wasn't abandoned, rather rebuilt on top of a virtual file system.
There are also other problems:
- a desktop app cannot look like a regular one, at least in Pharo.
- I am not sure how to glue native code, so when doing graphics stuff (this is what I really want to do with Pharo bc of its interactivity) I did not know how to consume APIs for drawing.
I do not have a lot of training admittedly. The interactivity looks super, super cool! But also you have to change a lot of habits.
Also, git is well-integrated.
It would rock to have native GUI. Also, if I could know how to draw graphics... That would be very nice also, because having an interactive environment for graphics is the most awesome stuff that Pharo can offer (me).
For 2D graphics, Pharo uses Cairo binding named Athens (and Roassal as another convinient layer above that).
For 3D, you may be interesting in Godotalk (https://www.youtube.com/watch?v=ncmERef0EFo, https://pharoweekly.wordpress.com/2021/02/03/godotalk/)
There's also native pharo smalltalk stuff like athens, woden, and maybe others... Personally I have moved to using Dolphin Smalltalk and raylib bindings there as it's been more performant and I prefer Dolphin over pharo. Dolphin also has good external interfacing support and is more of a native Windows OS smalltalk distro. If you're looking for building native windows looking applications on windows at least probably your best bet is dolphin and it's free and open source.
Will this mean HIDPI support too?
Pharo can make applications with a native look & feel - https://github.com/pharo-spec/Spec-Gtk.
Current Pharo has a very solid and powerful FFI, so using C libraries is easy.
I've had a daydream that some sort of data Swiss army knife/visualizer toolkit would be a fun project but it lacks the share ability of notebooks. Are there other operational sorts of things you use smalltalk for? I have a set of scripts and tools I use daily for work (things that slice and dice our data, perform various operational tasks against AWS, etc..) that I could see cooking in to some sort of smalltalk system but I haven't done it.
Yes. Correct. That was the intention of the language design. Here is described in detail by Dan Ingalls https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk....
Binary image-based development with serialisation, now we aren't all trying to make exes, should be more of a thing.
I often bootstrap experimental code with pry "built in" in various ways, e.g. with a small "prolog" with a few utility methods to hot-reload the code and set up basics to let me keep an editor in one window a pry session in another, make changes and just load the changes without dropping the current state so I can get instant feedback using the test data I already have loaded.
For the last two years or so I've used a text editor I wrote in Ruby instead of emacs which spins up a server process on first start that it then exposes via DrB (an IPC library for those not familiar with Ruby) that stores the contents of the buffers and persists them regularly. I did that both so I can implement multiple views on the same buffer without having the same client process keep everything open, and so that if anything crashes I'm unlikely to lose data.
But the side effect is that because DrB forwards exceptions too, and the editor itself is set up to catch all exceptions and throw me into pry, if something crashes my editor, I get a pry prompt where I can live-debug it on the real buffers, edit the editor with another instance of itself talking to the same backend server instance, and then hot-replace the code to see if it fixes the bug. For hard-to-reproduce bugs in particular it's amazing to be able to keep the buggy instance running while fixing the problem and figuring out which conditions causes it.
A: quick to "compile", so fast write-run-try loops, very runtime inspectable/updatable, less noisy syntax; but also easier to make runtime errors, larger test suites, less easy to refactor.
B: refactorability (especially with good IDE support), strong runtime correctness guarantees; but also long compile times, and no cool runtime inspection/update wizzardry.
It is not statically typed, so given a variable you can not know from the variable which type it currently holds. But in Ruby you can always know from the object itself which type it is.
That said there's a lot of work going on in Ruby to allow you to get more certainty statically about types. See e.g. Sorbet
What is strong typed is not well defined. But mostly people use it for langs that use the typesystem to prevent as many runtime errors as possible. Features like sum-types, and explicit nullability are often mentioned in that regard.
Ruby and Smalltalk have neither and do not so much try to avoid runtime errors. They tray to allow runtime inspection, and runtime fixing.
Both Ruby, Smalltalk and e. g. Python are generally regarded a strongly typed on the basis that no conflicts can occur to trigger undefined behaviour or runtime errors that are unrecoverable due to lack of type safety causing the runtime environment to be corrupted, coupled with the explicit typing of all data and limited implicit conversions.
In fact, a someone who has worked on a Ruby compiler, merely defining what meaningfully constitutes an error in Ruby is one of the big challenges with compiling it, because a program is free to define how to respond to any kind of type error in whichever way which makes most sense. Or to check and assert types however strictly you want.
While you can throw an exception you might call a runtime error, that does not imply a failed or crashed program unless you want it to, because the runtime environment does not get corrupted by it. A runtime error in Ruby can be part of the normal life cycle of an application that includes recovery.
My editor for example will not crash in the face of a "runtime error". A bug may trigger errors, sure, but they are all trapped and correctable from within the running editor itself. It's not unusual for me to keep my editor running for months at a time.
At the same time, nothing stops you from doing sum-types or explicit nullability in Ruby if you want to with some creative meta programming. People have built stricter type checks for Ruby as long as it has been around. It was almost a rite of passage for some time, but people tend to stop when they learn to use Ruby properly. A key aspect of learning Ruby properly is to learn to not fight the type system and only check the things that actually matters. When you do, you find a lot of type checks quickly becomes a smell.
But by your own criteria, the ability to completely trap and recover and prevent all runtime errors and the ability to define arbitrarily powerful typing systems, Ruby is strongly typed.
Just not statically typed.
Of course with 3.1 you can increasingly do static type checking too, though that will take time to mature and is not a replacement for strong typing at runtime.
"Programming Languages: Application and Interpretation" 2007, page 263-4
https://cs.brown.edu/~sk/Publications/Books/ProgLangs/2007-0...
Here are a few alternatives (equally unauthoritative, but demonstrating my use of the term):
"Strongly typed is a concept used to refer to a programming language that enforces strict restrictions on intermixing of values with differing data types. When such restrictions are violated and error (exception) occurs." ... "Examples of strongly typed languages in existence include Java, Ruby, Smalltalk and Python. In the case of Java, typing errors are detected during compilation Other programming languages, like Ruby, detect typing errors during the runtime." [1]
...
"Dynamically typed languages (where type checking happens at run time) can also be strongly typed. Note that in dynamically typed languages, values have types, not variables.
A weakly typed language has looser typing rules and may produce unpredictable or even erroneous results or may perform implicit type conversion at runtime." [2]
...
"A strongly-typed language is one in which variables are bound to specific data types, and will result in type errors if types do not match up as expected in the expression — regardless of when type checking occurs." [3]
This last one gives a clear delineation which mostly matches how I use it (e.g. C and PHP described as weakly typed - C allows you to override types and PHP does all kinds of nasty implicit conversions; conversely while specific Ruby API's may do conversions, the type system does not and do not allow implicit conversions, and do not allow you to coerce your way past the type system)
You can find all kinds of other variants, so sure, it's not a term that has a definition that everyone agrees about. Your linked article has a point that it's a term one need to define.
I however stand by my assessment that the definitions in most common use fits Ruby.
[1] https://www.techopedia.com/definition/24434/strongly-typed
[2] https://en.wikipedia.org/wiki/Strong_and_weak_typing
[3] https://medium.com/android-news/magic-lies-here-statically-t...
It's "a meaningless phrase", just stop.