I’m looking for (remote/hybrid) work (I’m in the Netherlands), be it consulting or something
more permanent. Please get in touch if you know of interesting ~principal-engineer-level work
for someone very capable with a slightly unusual career path!
You’re already here, so you’ve found my blog; also useful could
be my LinkedIn, my personal
website, my GitHub account, and the
Gitea instance I run for Syndicate-related projects.
Who I am and what I’m looking for
In a nutshell, I’m a programming languages and systems person who can both do the academic
thing and actually deliver projects.
Academics/open-source first: I’ve a PhD in programming language design; I worked with Alan
Kay’s group at VPRI/CDG/HARC for a bit; I built Syndicate (https://syndicate-lang.org/) and
Synit (https://synit.org/) recently; and all told, I like challenging, artful, interesting,
offbeat projects. I’m also good at them.
The other (industry) side of me: I wrote big chunks of the RabbitMQ ecosystem back in the day.
Wrote the code, built the website, maintained the machines, all that startup-to-exit tech
stuff. I’ve also done a lot of normal consultancy as part of LShift, as well as more recently.
To put it bluntly, I’m also good at this side of things: at designing and building software
that people actually want to use.
The wolves aren’t at the door. My current contracts are coming to an end though, and I’d love
to find something a bit more stable that isn’t just, you know, writing
AbstractBeanFactoryFactoryBeans for a living? I’m versatile, but I’m looking for a good
match. Actually, I’d happily trade money (to a point) for something really challenging and
creative.
From time to time I need to expose a development web site or web service to the world. In the
past, I’ve used ngrok for that, and of course long ago I built
ReverseHTTP which is somewhere in the same ballpark, but I recently
got fed up with the state of affairs and decided to see whether there was something simple I
could run myself to do the job.
Minimal, self-hosted, 0-config alternative to ngrok. Caddy+OpenSSH+50 lines of Python.
It really is desperately simple. A beautiful bit of engineering. At its heart, it scripts
Caddy’s API to add and remove tunnels on the fly. When you SSH into
your server, you invoke the script, and for the duration of the SSH connection, a subdomain of
your server’s domain forwards traffic across the SSH link.
I’ve forked the code for myself. So far, I haven’t
changed much: the script cleans up stale registrations at startup, as well as at exit, in case
a previous connection was interrupted somehow; and I’ve added support for forwarding to local
TLS services, with optional “insecure-mode” for avoiding certificate identity checks.
To get it running on a VM in the cloud, install Caddy (there’s a
caddy package for Debian bookworm and sid), then
disable the systemd caddy service and enable the caddy-api service:
Set up a wildcard DNS record for your server - something like *.demo.example.com. Each tunnel
will be made available on a subdomain of demo.example.com.
Then use the API to upload a simple “global” config. Here’s mine:
I wrapped that up in a tiny script so that I didn’t have to remember the details of that
incantation, but it’s simple enough that you could easily just type it in the terminal each
time.
Many thanks to Anders Pitman for a really nice piece of software!
Apparently Peter Henderson’s 1982 paper “Purely Functional Operating Systems” is hard to find
online. Some years ago, during my PhD research, I scanned the paper from a physical copy in the
university library. Here’s the scan I made.
The tool is great, but it uses perf to record and analyze profile
data, and
perf on Debian has a performance problem:
when not linked against libbfd, it shells out to addr2line for
every address it needs to look up. Thousands and thousands and
thousands of incredibly short-lived processes.
A big part of the change is to fix the confusing terminology used in
the project. From now on, I’ll try to stick to the following:
Syndicated actors, and the Syndicated Actor model: Like regular
actors, but with replicated state, i.e. assertions connecting peers
rather than just messages.
The notion of a dataspace: A particular kind of behaviour of a
syndicated actor: tracks assertions made to it, and reacts to
assertions of interest by responding with previously-learned facts.
Structuring of a syndicated actor via conversational concurrency
and facets: Programming actors, syndicated or not, gets to be a
bit of a mess if you’re restricted to using a monolithic behaviour
function. Facets let you split up an actor’s behaviour into
reusable composable pieces. Conversational concurrency guides you
in how to carve it up.
Language support for syndicated actors and conversational
concurrency: the Syndicate DSL.
Back in October 2020, I built an on-screen keyboard for Squeak
Smalltalk. It ended up being about 230 lines
of code (!) in total.
I’ve been using Smalltalk as the primary UI for my experimental
cellphone software stack, and I needed a way to type input.
Using VNC to develop on the phone works fine, but
to work on the device itself - both for day-to-day phone tasks as well
as developing the system - I need a proper on-device keyboard.
This video shows the keyboard in action. As you can see,
it’s not perfect! At one point I tapped ctrl while typing, leading
to an unexpected pop-up menu appearing.
But the basic idea is sound. (Aside: why aren’t there any Android
keyboards that are laid out like a PC keyboard, with punctuation in
the right place, etc.?)
The usual shortcuts like Alt-P for “print it”, Alt-D for “do it”
and Alt-I for “inspect it” all work for evaluating snippets of
Smalltalk interactively.
The keyboard integrates with my previously-written touchscreen support
code, with the red/blue/yellow modifier buttons affecting touches,
making them into simulated left-/middle-/right-clicks, and with
keyboard focus changes auto-popping-up and -down the keyboard.
Simulating mouse clicks is a temporary state of affairs that lets me
use the touchscreen reasonably naturally, making use of context menus
and so on, without having to make invasive changes to the input
pipeline of Squeak’s
Morphic.
How it works
Class OnScreenKeyboardMorph synthesizes keyboard events and injects
them into the Morphic world.
You can have arbitrarily many keyboards
instantiated, but there’s a global one living in a
flap at the bottom of the
screen. That’s the blue tab at the bottom left of the screen you can
see in the video.
I had already written code to read from /dev/input/eventN,
tracking each multitouch contact separately. A subclass of HandMorph
overrides newKeyboardFocus: to pop the keyboard up and down as the
keyboard focus comes and goes:
I haven’t released the whole package yet, because it’s all very much
in flux, but if you’re interested, you can take a look at the
changeset for the Keyboard-related code here:
LinuxIO-Morphic-KeyboardMorph.st
UPDATE: Full code available at
this gist
(also embedded below).
Say you have the following
TypeScript interface I that you
want to invoke remotely by passing messages of type M; or that you
receive messages of type M and want to handle them using an object
of type I:
TL;DR TypeScript lets us define generic types Messages and
Methods such that M = Messages<I> and I = Methods<M>. Read on
for the details.
Interface ⟶ Messages
Let’s start with what, for me, has been the common case: given an
interface type, automatically produce the type of messages that can be
sent to implementors of the interface.
I’ve taken a leaf out of Smalltalk’s book, and made a message include
a selector, the name of the method the message intends to invoke,
and some args, the provided arguments to the method. The Args
extends never[] check is to help type inference deduce the empty
argument tuple: without it, the type system won’t complain about
missing arguments.
I’ve also added a callback to Message. The technique I describe
here can be further extended to “asynchronous” or callbackless
settings with minor modifications.
The next definition, of type Messages<I>, is where the magic
happens. It expands to a union of Messages representing the methods
defined in I:
MessagesProduct is a mapped type that describes a modified
interface, where all (and only) the method properties of interface
I are rewritten to have a Message as their type, but keeping
the same key;
then, the ...[keyof I] part in the definition of Messages uses
index types to set up a union type built from all
the value types (“indexed access operator”) associated with all the
keys in I (“index type query operator”).
It’s a mapped type, again, that maps union members that have
Message type to an appropriate function signature. It takes
advantage of TypeScript’s automatic distributivity: a union of
products gets rewritten to be a product of unions. Then, in the
conditional typeM extends Message<...> ? ..., it projects out
just exactly the member of interest again.2
This time we use the mapped type as-is instead of re-projecting it
into a union using indexed access like we did with MessagesProduct
above.
Type-safe interpretation of messages
Now we have types for our interfaces, and types for the messages that
match them, can we write a type-safe generic perform function? Yes,
we can!
Roundtripping works too: both Methods<Messages<I>> and
Messages<Methods<M>> give what you expect.
TypeScript is really cool
It’s a fully-fledged, ergonomic realization of the research started by
Sam Tobin-Hochstadt, who invented
Occurrence Typing,
the technology at the heart of TypeScript.
Then, building on the language itself, emacs with
tide,
flycheck, and
company makes for a very pleasant
IDE.4
Congratulations to Sam, whose ideas really have worked out amazingly
well, and to the TypeScript team for producing such a polished and
pleasant language.
Appendix: Full code implementing this idea
This module implements the idea described in this article, extended
with the notion of EventMessages, which don’t have a callback.
// This Tuple type (and tuple() function) is a hack to induce// TypeScript to infer tuple types rather than array types. (Source:// https://github.com/microsoft/TypeScript/issues/27179#issuecomment-422606990)//// Without it, [123, 'hi', true] will often get the type (string |// number | boolean)[] instead of [number, string, boolean].//exporttypeTuple=any[]|[any];exportconsttuple=<AextendsTuple>(...args:A)=>args;// Type ValidSelector captures TypeScript's notion of a valid object// property name.//exporttypeValidSelector=string|number|symbol;exporttypeEventMessage<SelectorextendsValidSelector,Argsextendsany[]>={selector:Selector,args:Args};exporttypeRequestMessage<SelectorextendsValidSelector,Argsextendsany[],ResultextendsExclude<any,void>>={selector:Selector,args:Args,callback:(result:Result)=>void};exporttypeMessage<SelectorextendsValidSelector,Argsextendsany[],Result>=voidextendsResult?EventMessage<Selector,Args>:RequestMessage<Selector,Args,Result>;// Function message() is needed for similar reasons to tuple() above:// to help TypeScript infer the correct literal type for the selector// (as well as the arguments).//exportconstmessage=<SextendsValidSelector,AextendsTuple,R>(m:Message<S,A,R>)=>m;typeMessagesProduct<I,ContextArgsextendsany[]>={[KinkeyofI]:(I[K]extends(...args:[...ContextArgs,...inferP])=>inferQ?Message<K,P,Q>:never);};exporttypeMessages<I,ContextArgsextendsany[]=[]>=MessagesProduct<I,ContextArgs>[keyofI];exporttypeMethods<Mextends{selector:ValidSelector},ContextArgsextendsany[]=[]>={[SinM['selector']]:(MextendsRequestMessage<S,inferP,inferR>?(voidextendsR?never:(...args:[...ContextArgs,...P])=>R):(MextendsEventMessage<S,inferP>?(...args:[...ContextArgs,...P])=>void:never));};exportfunctionperform<IextendsMethods<M,ContextArgs>,SextendsValidSelector,MextendsRequestMessage<S,Tuple,any>,ContextArgsextendsany[]=[]>(i:I,m:M,...ctxt:ContextArgs):(MextendsRequestMessage<S,Tuple,inferR>?R:never);exportfunctionperform<IextendsMethods<M,ContextArgs>,SextendsValidSelector,MextendsEventMessage<S,Tuple>,ContextArgsextendsany[]=[]>(i:I,m:M,...ctxt:ContextArgs):void;exportfunctionperform<IextendsMethods<M,ContextArgs>,SextendsValidSelector,MextendsRequestMessage<S,Tuple,any>,ContextArgsextendsany[]=[]>(i:I,m:M,...ctxt:ContextArgs):any{constr=i[m.selector](...ctxt,...m.args);m.callback?.(r);returnr;}
Well, at least in TypeScript v4.x, anyway. I
don’t know about earlier versions. ↩
Actually I’ll admit to not being quite sure that
this is what’s really going on here. TypeScript’s unions feel a
bit murky: there’s been more than one occasion I’ve been surprised
at what a union-of-products has been automatically “simplified”
(?) into. ↩
Hey, what’s going on with those named tuple
slots? I would have expected a tuple type like [number, string]
not to be able to have names attached to the slots, but it turns
out I’m wrong and the compiler at least internally propagates
names in some circumstances! It even re-uses them if you convert a
Messages<I> back into an interface, Methods<Messages<I>>… ↩
Here’s my .emacs TypeScript setup, based on the examples in the tide manual:
Recently on HN,
rbanffy brought up a form of the old chestnut about crashing the
image by simply executing true become: false.
It turns out it’s no longer true!
In the latest Squeak,
True:=False.
doesn’t work – the compiler complains that you can’t assign into a
read-only variable. So let’s try this:
Smalltalkat:#Trueput:False.
But now the metaprogramming system complains you’re trying to modify a
read-only binding! So we view source on ClassBinding»value:, and see
that a resumable exception is being used to guard the modification, so
let’s explicitly signal that we really want to modify that
binding:
But the image keeps running! Use of the literal class True seems to
be rare enough that things are OK for at least several minutes after
the change.
Doing this, however, definitely should immediately torpedo things:
truebecome:false.
… huh. It didn’t work. It used to! Again, on this current Squeak
version, we see a different behaviour. This time, it says Cannot
execute #elementsExchangeIdentityWith: on read-only object #(false).
So we’ll have to try harder:
truebecomeForward:false.
That doesn’t work either! Same error as for #become:.
Welp, I’m actually all out of ways to crash this modern image in
analogous ways to the easy pitfalls of images of yesteryear…
P.S. Running the following command,
ln-sf /dev/zero /lib/x86_64-linux-gnu/libc.so.6
analogous to True := False, definitely does have an analogous
effect on a similarly-“alive” Unix image :-) . I just tried it on a
scratch VM; the results are pretty intimidating!