Skip to content

Instantly share code, notes, and snippets.

@dominictarr
Created March 11, 2015 21:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dominictarr/2e22f76b9c75cad46a74 to your computer and use it in GitHub Desktop.
Save dominictarr/2e22f76b9c75cad46a74 to your computer and use it in GitHub Desktop.
Strongly ordered programming

programming is hard, but async programming is harder. Don't listen to people tell you about "callback hell". They are only in the most outer circles of async programming hell. As you get deeper into async systems, you'll see that what makes it hard is that there are many possible orderings for events to occur in. How many? well if the time taken for N processes is independant, then there are N! (factorial) possible orderings!

but what if we could at least be precise about what sort of orderings are expected?

so, forEach is a function that takes a function and calls it maybe many times.

Array.prototype.forEach = function (<many> fn) {
  var l = this.length
  for(var i = 0; i < l; i++)
    fn(this[i], i, this)
}

A correct node.js async function also has precise ordering semantics, you pass a function a callback, and it will eventually call it, but not more than once.

fs.readFile = function (file, enc, <once> cb) {
  _readFile(file, enc, cb)
}

One failure case is that a function you where expecting to be called just once is actually called more than once, however, as javascript is dynamically typed, it's also dynamically ordered. You must simply be self-disciplined about ordering.

Confession: I lied just before, because the function passed to forEach can be called zero times - if the array is empty. Also it will not be called after the function returns. So the proper order-type for that function would look different, maybe like this:

Array.prototype.forEach = function (<maybe many before(return)> fn) {
  var l = this.length
  for(var i = 0; i < l; i++)
    fn(this[i], i, this)
}

But it's async systems that are more interesting. Take a stream. A stream is like Array#forEach, except in time. it doesn't have a return event, but it has an end event, and an error. Here is a simplified stream api, since a more typical node stream (with it's on(eventname,...) api would be difficult to represent)

var createStream = function () {
  return {
    pipe: function (<{<maybe many before(end, error)> write, <once unless(error)> end, <once unless(end)> error}> stream ) {

    },
    write: <many before (end)> function (data) {
      
    },
    end: <once unless(error)> function () {

    },
    error: <once unless(end)> function (err) {

    }
  }
}

wow, that was a lot more complicated. lets say that unless means that if the unless event happens, then this event should not happen.

Okay, but this is still pretty elementary. We want streams to be correct. But then we want to build applications and systems out of streams. Often, we want a server, where the server has a close event, but it can't happen until after all the other stream have ended.

function createServer (<maybe many before(close)> connect(<{once before (close) > end}> stream)) {
  return {
    listen: ...,
    close: function (once: cb) {
      //we don't need to specify here that this event must happen after all the streams have closed,
      //because we have that assurance pointing the 
    }
  }
}

create a server with a function that is called maybe many (0-N) times with a stream that must end before the server's close event may occur.


As well as being useful to check correctness of async orderings, this may also have use for preventing GC leaks, as that is also about whether or not a thing eventually happens. If a function is declared to be only called once, then after it is called, it can be GC'd.

It would be pretty easy to implement this all with runtime checks. but what would probably be much more useful, is for our system to tell us that will execute within the constraints we asked for.

@NHQ
Copy link

NHQ commented Mar 11, 2015

It looks like you'd have to leave all the unless functions alone.

I found this code in a diary I stole from my neighbor at the inn.

var ctxs = {}
var call = Function.prototype.call
Function.prototype.call = function(ctx, args){
  if(!(ctxs[hash(ctx)])) ctxs[hash(ctx)] = {times:0}
  ctxs[hash(ctx)].times++
  return call(ctx, args)
}

@dominictarr
Copy link
Author

@NHQ oh no there would be none of that hankypanky

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