Skip to content

Instantly share code, notes, and snippets.

@acunniffe
Last active October 21, 2018 18:40
Show Gist options
  • Save acunniffe/031caf85261a790b7406e1c0f5658605 to your computer and use it in GitHub Desktop.
Save acunniffe/031caf85261a790b7406e1c0f5658605 to your computer and use it in GitHub Desktop.
Glue Lang (Abstract)

Problems:

  • a lot of the complexity in modern programming comes from connecting various systems together & current paradigms aren't well suited for this kind of work
  • it's easy to share code, but much harder to package up & share features seamlessly

Key Idea: Compile graphs should be shared between projects so compilers can understand how to connect to the higher level interfaces a program exposes.

The Paradox

General purpose programming languages have a wide range of uses because they are built on foundational abstractions. But this flexibility comes at a cost: there's a lot of distance between those higher level abstractions that we deal with (APIs, queries, data models, streams, HTTP requests, etc) and the language itself. This distance is the reason it's ridiculously hard to do something like code completion or compile-time-feedback for a higher order construct like a request to a specific endpoint.

It's easy to code complete a function because it's represented by a FunctionNode in the AST, but there's no direct mapping between a bunch of AST nodes for an HTTP request and concept we have in our heads of how that request should function. This is part of the reason I developed Optic & when our short term roadmap is fully realized we should be able to handle most of what I described as a problem area; however, I wanted to step back and think about what a language that made Optic's current capabilities obsolete might look like.

Glue Lang

I haven't thought much about the syntax of this language, but it's almost certainly functional & strongly typed.

The language would have 3 structural components:

  1. External interfaces
  2. Pure functions
  3. Client Actors

which notably excludes: classes, any kind of inheritance, user definable global variables/other ways to store global state

External Interfaces

Today programs declare their external interfaces imperatively, your code runs and at some point you hit code like .bind() or .listen() that binds the process to a certain port. That code routes / handles every API connection using the API endpoints it collected at runtime as a reference. We then manually document these API endpoints in some declarative format like Swagger and share that with other people so they know how to connect to our APIs.

This is kind of backwards IMHO. If we want to write reliable software that is easy to connect to, external interfaces should be strict and fully understood at compile time.

In Glue you'd define something like a method contract for each external interface in a declarative DSL:

external SignUpUser(name: String, email: Email, password: Password): UserCreated, Errors {   //notice how we allow multiple return types 
    trySignUp(name, email, password) { //relies on matching, not if's to discern different return types
       case createdUser: User => UserCreated(id)
       case InvalidUser(errors) => Errors(errors)
    }
}

//Responses: 
UserCreated(id: String)
Errors(errors: [String])

When the program is compiled EVERY interface marked as external will get placed into a graph along with the fully resolved data types it relies on (instead of 'User' type it will appear in the graph as name, email, password... probably using something like JSON schema). This graph would be dependency free so any other Glue program/compiler instance could understand how to call the interface.

Each glue program would publish this graph on some predetermined port ie :5555.

When you decide to connect to this other system you can define a connector like so in the connections.glue file. This file is run first and then the connections are injected as global vars throughout your code.

connector MyBackend('hostname', {...connect object for auth / configurable options}

The Glue compiler will download the graph from the specified hostname and use it as part of the final compiler graph for validation + autocomplete. So from anywhere in the project I can call the available:

MyBackend.SignUpUser('My Name')  //will result in a compile error
MyBackend.SignUpUser('My Name', email, password) //compiles because the interface lines up

Pure functions

You can use pure functions to break apart the code / do the heavy lifting for your external interfaces. Glue doesn't allow global variables and it strongly advises against maintaining any internal state. What makes a Glue function special is that it only has access to its parameters and variables declared within the function. There can be no side effects because there aren't global variables to store state that'd modify its behavior.

The decision to insist on the relative purity of functions may seem odd but I think the strict external interfaces remove the need for most global variables. Those that do make sense can come from a global config object with immutable values.

Client Actors

Maintaining sessions is one of the main reason to have some notion of a global state. Glue replaces this with client Actors which get instantiated each time a client connects to your app and persist across multiple requests just like a normal session. The mechanics of this will be handled under the hood.

The client actor will be passed implicitly through the call stack and you can send it different messages to mutate its internal state as you see fit. You can also switch between different kinds of Client Actors (ie one for logged out, logged in, admin, etc).

client LoggedOut {
   
   initialState {
     loginAttempts: Int = 0
   }
   
   messageHandler {
      case loggedIn(id, token) => switchTo(LoggedIn, {id, token})
   }
   
   allowedInterfaces = [signUp, login]
}

Bolt-ons

Because external interfaces are defined declaratively it’d be much easier to ‘bolt-on’ someone else’s auth system, payment processing, push notification manager, etc. It’d be as easy as turning something on & defining options and since its loosely coupled it’d be easier to change out these components as you saw fit without requiring huge changes.

Adoption challenges...

If every app was ported to something like Glue we could all reap the benefits, but it's unlikely older system will be rewritten. In the meantime if Glue was developed, macros could be created to expose a traditional REST or Websocket interface for older clients -- these would come with automatically generated docs to help you connect. Tools like Optic could also help make older APIs more Glue like by extracting a similar API graph using static analysis. In the short/medium term Glue would likely need some of these dirty workarounds in place to get adoption, but in the long term I suspect the most popular languages to generate universally consumable graphs that describe their higher level public interfaces.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment