by kragen | 2025-10-18 02:41:17+00:00
Nice Onwards! paper. But to my taste it's a bit slow-paced, taking 10 pages to, as I see it, start getting to the point:

self=$1 tmp1=$(send $self height) tmp2=$(send $tmp1 / float/3.0) crossHeight=$(send $tmp2 rounded) tmp4=$(send int/0 \@ $crossHeight) tmp5=$(send $self bounds) send $tmp5 insetBy: $tmp

The rest of the paper seems to be about how to make that, or its moral equivalent, not just work but work efficiently.

When I implemented this idea in 02002 (in shell scripts) the clumsiest thing was lifetime management of the objects, as Jakubovic anticipates in the paper. I had thought that I put symlinks to the method scripts directly into each object directory, making it a prototype-based OO system, but no, it actually implemented walking a prototype chain, just as Jakubovic's proposed system walks a superclass chain. And, just as Jakubovic did, I passed the receiver's pathname as the first argument to the method executable, passed arguments on the command line, and returned results on stdout. I called my version "shoo"⁴ but it wasn't original to me, inspired by Martin Hinsch's "woosh"⁵:

> shoo makes your filesystem and shell into a prototype-based object-oriented programming environment with dynamic method binding, transparent persistence (but terrible memory management!), multiple inheritance, and local-network distributed objects (if you have NFS, and only the state is distributed --- not the computation).

> Unfortunately, it still doesn't make the shell into a decent general-purpose programming language.

shoo was painfully slow. The `testscript` in the package takes 4.5 seconds on my cellphone in Termux, and all it does is instantiate two rectangular points, two polar points, and three other objects, and call some methods on them. I'm pleased to see that it does at least pass, but it might be reaching 10 or 20 message sends per second.

In shoo, the pathname at which to create the new object was an argument to the new and derive methods, as in Tk. I think something like this is probably a good idea if you want to use this approach for something; otherwise it would be very difficult to clean up. It also makes the shell script quite a bit more readable because you don't have to capture return values so often:

oo newobj pr oo basicobject derive thirdobj oo thirdobj defprop acting "What's my motivation?" oo thirdobj acting oo newobj derivefrom thirdobj oo newobj acting oo anotherobj die oo newobj die

As in Self, adding a property to an object (such as `acting`) adds corresponding getters and setters. The `die` method was one I defined in basicobject to rm -rf the receiver, due to the lack of any GC.

Other code did have to use the rather clumsy bash syntax for capturing output. I didn't attempt to make numbers and strings into objects as Jakubovic wants to; here's the method rectpointclass/r, which calls the x and y accessors:

#!/bin/bash -e # generate radius for a rectangular point. x="$(oo "$1" x)" y="$(oo "$1" y)" echo "e(l($x * $x + $y * $y)/2)" | bc -l

Jakubovic mentions that creating a million Unix processes will cause "bad things to happen" but even on my laptop creating and destroying a million processes only takes about a minute (httpdito² can serve 20000 hits per second, creating all the children from a single core; C programs are much slower³, about 7000 fork/exit/waits per second, presumably because they map more pages) and I think you could do it in a second with AWS Lambda. Possibly you don't want them all to exist at once, but of course the original Smalltalk systems couldn't do that either—their object references were only 16 bits, so 65536 objects was the theoretical max, and they ran on machines without enough RAM for even that much.

There are several factual errors in the paper, but I think the only consequential one is the assertion that FUSE makes it possible to implement files as efficiently as Smalltalk objects. It's true that io_uring has made it possible to eliminate the system call context switching overhead from file operations, but even within the kernel, dispatching a user I/O request to a filesystem is at least an order of magnitude slower than a Smalltalk message send on the same hardware, and FUSE adds additional overhead.

Some Smalltalk object memories segregated objects by class, so that all the objects of the same class were contiguous in memory, like an array of structs of the same type. It might be reasonable to put many objects containing the same set of fields into an Apache Arrow file so that they can be read by processes without having to read in the whole file, relying on memory mapping. (Arrow is a language-independent in-memory data format.) Or you could just accept the small overhead of Linux files, which might be 256 bytes of disk and, even without io_uring, only about four 300ns system calls¹ to read their contents: 1.2μs (open, read, read, close). It isn't fast enough to do everything you can do with a Smalltalk system today, but it's fast enough to do everything you could do with a Smalltalk system in the 70s.

As long as you don't try to write it in bash. LuaJIT might be a reasonable alternative, combining easily editable scripts with a reasonable library system and reasonable startup time.

______

¹ http://canonical.org/~kragen/sw/dev3/syscallovh.c

² http://canonical.org/~kragen/sw/dev3/server.s

³ http://canonical.org/~kragen/sw/dev3/forkovh.c

⁴ https://www.mail-archive.com/kragen-hacks@canonical.org/msg0...

⁵ http://web.archive.org/web/20040409072028/http://wosx30.eco-...