git @ Cat's Eye Technologies Unlikely / master doc / Report on Unlikely, 2026.md
master

Tree @master (Download .tar.gz)

Report on Unlikely, 2026.md @masterview markup · raw · history · blame

Report on Unlikely, 2026

The Unlikely programming language has been in existence for many years now, but has remained largely unimplemented, with there being only a parser/static analyzer, and the beginnings of a sketch of an interpreter.

In 2026 I decided that a proper implementation of this language was far overdue, so I started developing an interpreter.

This led to the discovery of several things about the existing state of Unlikely, which I'll chronicle in this document.

Parser/Static Analyzer

Minor parsing bugs

First, due to an unforgivingness in the lexical tokenizer, every Unlikely source file needed to have some whitespace at the end of the file, or it wouldn't parse. This is minor and has been fixed.

There was also an error in the parser which prevented the correct parsing of lists of arguments to a method (it wasn't consuming the comma separating these arguments). This is also minor and has been fixed.

Identifier scope in continues

A weirder situation was in the parsing of expressions. The parser itself does some lexical analysis as it parses; one of these things is resolving identifiers in expressions. It resolves them based on the scope of the containing method's class definition.

However, when parsing a continue (e.g. goto foo.continue(bar)), the "containing method" of the expressions was considered to be the method being called rather than the method of which the continue is part.

This limits the things that can be passed to the method to newly-constructed instances and properties that are defined on the destination class whose names coincide with properties defined on the source class. It looks like it was not noticed, because all continues in example programs and tests do actually fall into these two categories.

This goes against the usual intuitions of passing values to a function - not that that should be the deciding factor, in an experimental language - but since the spec does not mention this as a defining feature, and since it is quite limiting and seems to go against the usual idea of parameter passing quite strongly, I've treated it as a bug and fixed it.

Dependency injection

I'll write about this more fully under "Language Definition", but the static analyzer tries to resolve injected classes without actually being able to do so. Dependency injection is usually a runtime thing, but it is a "more static" concept in Unlikely, in the sense that only classes can be injected, and there are no class variables. But to treat it statically, one would need to examine each instantiation site of a class, and look at what dependant classes it is injecting. Based on the comments in the static analyzer, the author (me) clearly thought that the static analysis was accomplishing that, but it isn't accomplishing that.

This is probably not worth fixing in the static analyzer since we can also handle dependency injection at runtime (even though it is theoretically analyzable ahead-of-time) and that is what I've done in the interpreter.

Interpreter

Persistence of properties

Only a sketch of an interpreter existed, but its code suggested that, every time a method is executed, not only are the class properties associated with the arguments populated with the actual arguments passed in, all properties on the object are initialized to default values. The resulting behaviour is that properties can't reliably persist on objects.

It's very likely this was just an error in the sketch, and the initialization should have been located elsewhere.

The language description does not say that properties should persist between invokations of their methods. The Loop built-ins do seem to assume their state persists between invokations. But those are built-ins, which are already assumed to have functionality that pure Unlikely programs don't have. But in the discussion it is mentioned that they could probably be implemented in pure Unlikely.

From an object-oriented point of view, it is surprising for object state to fail to persist. On the other hand, if we look at these things as continuations, it is less surprising. Taking it as a restriction leads to a more state-passing, functional style, and if we disallow property persistence, it might lead to a more "interesting" style of programs, where data is shuttled around because it can't be kept in a Continuation object and mutated.

However, on the balance of what the spec suggests, it probably really was just an error in the sketch, and properties are expected to persist. I have implemented the interpreter accordingly, but left in the original logic from the sketch under an internal, disabled flag called AMNESIAC_MODE.

Language Definition

Dependency injection

Given that built-in classes need not be implemented in Unlikely, it's unclear why any of them name any dependant classes. (When I instantiate Print, should I expect to be able to pass it a subclass of Chain and have it use that subclass' behavior instead? How does Print use Chain, anyway? I mean, doesn't this need to be specified, in order for it to make sense for me to inject a subclass of Chain in an informed fashion?)

Under "Inheritance", the spec says "The subclass may not inject dependencies when inheriting from a superclass." This could stand being clarified, because "injection" is something that happens at instantiation sites, not class definitions. I believe it was meant to impart that a subclass cannot override or modify the dependencies that the superclass has already declared. Indeed, if it tries to name a dependant class that one of its superclasses had already named, the static analyzer rejects this as an error. So it cannot even bring in two dependencies for the same class, and allow clients to inject them independently (which would arguably make sense.)

I have added some unit tests for subclassing and dependency injection, to try to clarify the behaviour. It appears that the static analyzer is not incorrect, but the behaviour may be unexpected, due to built-ins having dependant classes (including Passive, which itself feels too simple to be a dependant - but it's not concrete, like its subclasses, so it is), and depedants being given in the order they are listed in the class declaration, and due to already-fixed dependants being skipped in that list.

Overriding methods

Under "Inheritance", the spec says "A subclass may override methods that it inherits from its superclass", and a few other things about overriding; but in the "Discussion" section we see: "Because properties cannot be redefined in subclasses, and because parameters to methods are just syntactic sugar for properties, methods cannot be overloaded." Which, if true, would sort of push the design away from an experiment and closer to a farce. But, it's not strictly true: you can overload a method, you just can't change its signature.

This could probably use a unit test or two to directly confirm the behaviour.

Built-in classes

Loop

In writing the interpreter I've left out ForLoop or WhileLoop. They're not necessary for looping (see countdown.unlikely), their design is suspect (the design of WhileLoop in particular seems particularly goofy), and I have it on good authority that it's possible to write them in pure Unlikely. Wrtiting them in pure Unlikely is thus left as an exercise to the reader.

Stop

Another decision taken when building the interpreter was that if Stop receives an integer, then that integer becomes the exit code; but if it receives a value of some other type, the exit code is 0. This was mainly to keep the examples simple.

Example programs

countdown.unlikely was written incorrectly. This has been fixed. Its existence substitutes for a Turing-completeness proof, since it should be fairly straightforward to look at it and see how one could construct an arbitrary Minsky machine in Unlikely. (The spec doesn't say integers are unbounded, but it also doesn't say they aren't.)

echo.unlikely and cat.unlikely were added to demonstrate Input.

fibonacci.unlikely, which has historically been incomplete, a "suggested serving" perhaps, was removed. This was after a second attempt to write it was undertaken, then abandoned. Writing Fibonacci in continuation-passing style is somewhat abstruse, but not outlandishly so. But the functions in it are mutually recursive. In Unlikely, one would need to use the "forward declaration" feature to tie them together; however, because they must also declare their dependant classes, which are also mutually recursive, we must do something tricky to get around that. The "obvious" way to get around it - maybe - would be to define generic base classes that are mutually recursive, and then inject subclasses of those base classes; but this is definitely adding abstruse on top of abstruse, and it's not something I've yet had the patience to pull off. But, again, I have it on good authority that it is possible to write it (directly in mutually-recursive continuation-passing style) in Unlikely, so again, it is left as an exercise to the (motivated) reader.