Librarian of Alexandria

experimental

This is an entry from a list of projects I hope to some day finish. Return to the backburnered projects index.

What is it? A “web framework”, but over SSH instead of HTTP.

What are web frameworks about, on a basic level? You're taking a protocol meant originally for serving simple documents in a hierarchical file system, and instead of serving those documents you're doing various computations and responding with dynamic data. Your web framework needn't interpret a GET /foo/bar as actually looking up a file bar in a directory foo: you could have code attached that does whatever you want!

Cube Cotillion follows the same basic idea except with a different protocol: SSH. Usually, when you establish an SSH connection, you get dropped to a shell and the stuff you type gets interpreted as command-like invocations. Cube Cotillion implements an SSH server, but instead of passing strings to a shell, it instead lets you write arbitrary computations keyed on the commands you've written. If someone writes ssh my-cc-server foo bar, then instead of trying to run the command foo with the argument bar, you can do, well, whatever you want!

Cube Cotillion is implemented as a Haskell library that does pattern-matching over the command passed in over SSH and can invoke callbacks as necessary. Here's a simple example: this server understands the command greet, with or without a parameter, and will respond with a greeting in kind, using the bs command to respond with a ByteString:

main :: IO ()
main = do
  key <- loadKey "server-keys"
  cubeCotillion 8080 key $ do
    cmd "greet" $ do
      bs "Hello, world!\n"
    cmd "greet :name" $ do
      name <- param "name"
      bs (mconcat ["Hello, ", name, "!\n"])

Like a web framework, we can now do whatever we want in response to these commands: there's no underlying shell we're passing commands to, and greet isn't dispatching to another executable, it's just a single process figuring out what to do with the strings and responding in kind. We could build applications on top of this that use structured data (e.g. responding with JSON blobs instead of simple lines) or keep various pieces of internal state. Why not?

Why write it? This is far more on the experimental side: I have no idea if this would be useful for anything at all!

The thing that I like about using SSH as a transport mechanism is that it necessarily has a notion of secure client identity. For certain applications, having a built-in mechanism for verifying client identity would be wonderful, since that's something that need to be built on top of HTTP applications again and again.

That said, I have no idea whether applications built on top of this system would be useful in practice or not. That was sort of the point: I wanted to experiment with using it as a tool for building personal server control, but would it be useful for anything else? Who knows!

I also never got around to designing the right API for doing anything with the client identity, or the ability to do interactive sessions at all. There's a whole design space there that might be fun to explore.

Why the name? I wrote this in Haskell, and there are multiple web frameworks in Haskell named after Star Trek characters, including Spock and Scotty. Given that this was an analogous but wrong project, I decided to choose a name from [Neil Cicierega's guide to the alien races in the Star Trek universe.

#backburner #software #experimental

This is an entry from a list of projects I hope to some day finish. Return to the backburnered projects index.

What is it? A programming language designed as a tiny experiment: what if a dynamic language were built to emulate the affordances of a static language with typeclasses or traits?

In practice, many dynamic languages are designed around storing data either in a handful of basic data structures—usually lists and associative arrays, sometimes adding a handful of others like sets—and then more sophisticated data modeling is done using some kind of object system. There are exceptions, of course—Erlang, for example, omits the object system entirely and sticks to simple data modeling plus processes—but it's definitely true of most of the commonly-used dynamic languages like Python and Ruby and JavaScript and Lua.

The experiment in Petri is that it uses a dynamic version of algebraic data types—which it calls tags, borrowing loosely from the Tulip language—and then provides a feature which allows functions to be implicitly looked up based on the tag. For example, the following program—using a syntax where keywords are marked explicitly with @1—creates a tag called Person, then defines how to print it to the screen using the Repr class:

@tag Person {name, age, pets}
@impl Repr(Person) {
  @fn repr(p) {
    "Person({p.name}, {p.age}, {p.pets})"
  }
}

We can now write a function which creates a Person and prints a representation of that person to the screen:

@fn main() {
  person := Person {name: "Ari", age: 33, pets: []};
  print(Repr::repr(person))
}

Underneath the hood, Repr is a map from tags to bundles of functions, so when we call Repr::repr, it'll look up the tag of its argument and find the relevant implementations of the functions, and then call that with the relevant parameter. In practice, we can also “open” any module-like thing—i.e. anything where we'd use :: to access a member—so we can also @open Repr in the root scope and then call repr instead of Repr::repr. (In practice, the prelude will pre-open several commonly-used classes, and also provide syntactic sugar for classes like Eq and Add.)

There's a little bit more practical sophistication to the typeclass mechanism, as well. For example, Eq is a typeclass with two “type” parameters, and it also provides a default implementation. It also does run-time introspection to find out whether a class has an implementation for two specific values! Also notice that, in the definition of Eq::eq, the parameters are capital letters, which reference the stand-in tag names that are parameters to the class Eq: this is how we indicate which argument tags to use to look up instances. It's possible to have methods that take extra parameters which are ignored for the purposes of instance lookup, in which case those parameters can be written as _.

@class Eq(A, B) {
  @fn eq(A, B);
  @default eq(a, b) {
    @if implemented(Eq(b, a)) {
      return Eq::eq(b, a)
    }
    False
  }
}

This also doesn't go over some other useful features for writing actual programs (e.g. a basic namespace mechanism, how file imports work, what other parts of the stdlib look like) but it gets at the core of the idea: a language that builds a dynamic version that tries to replicate the affordances provided by features like Haskell's typeclasses or Rust's traits.

Why write it? To be totally clear, Petri is more of a UI experiment than any kind of programming language innovation! After all, there's nothing new semantically in this idea: it's just multimethods. In terms of what you can express, there's almost nothing you get that's not already present in CLOS. The thing I'm interested in trying with Petri is more about affordances, figuring out what programs look like when these features are expressed in this way. After all, Python and Ruby and (modern) JavaScript are all languages with very similar basic features—dynamic types, object systems which provide sugar over hashtables of values and functions, some simple built-in data structures, and so forth—but programs in all three can look radically different because of the specific way those low-level building-blocks are combined. What if we took a tack that, while not unique, was still several steps outside of the Python/Ruby/JavaScript world?

I've got a background in both static and dynamic languages: I used to program in a lot of Python and Scheme, but my first two jobs used primarily Haskell and eventually smatterings of Rust, and more recently I've been working on Ruby-related infrastructure at Stripe, which means I spend most of my work time these days in a dynamic language with a static type system over top of it. I'm always interested in things that can bring affordances from one into the other, whether that means bringing static features to dynamic languages (as here) or dynamic features to static languages.

Admittedly, I doubt I'd actually use this language for much, but I still love tiny experimental programming languages. It'd be fun to see how far it could be pushed in practice, to see what at least medium-sized example programs look like in something like Petri.

Why the name? It's an miniature experiment, and miniature experiments are often found in Petri dishes.

#backburner #software #language #experimental


  1. This is also a feature borrowed from Tulip, and one that I think is very cool in general but especially for languages which are still growing. Modern languages often have a need to expand their vocabulary of keywords, which means they sometimes reserve a set keywords in case they're ever needed later, but even still languages might need to either context-sensitive keywords to introduce keywords without breaking existing programs or break backwards-compatibility to introduce new keywords which weren't previously reserved. None of these are inherently bad choices, to be clear, but I like the Tulip approach: if all keywords are in a particular namespace, then there's never any ambiguity or chance that a future revision will break your program since your variable is now reserved. Admittedly, it also is best when there's a syntax with a relatively low number of keywords, like the Haskell style. If you used this same approach for something like SQL, your program would be incredibly noisy with the sheer number of @ characters!