3

I've got trouble wrapping my head around a certain problem:

In my data-flow app (a visual editor), I have both autonomous objects which communicate though ports via unordered simultaneous messages and thus represent an asynchronous system.

in this picture, f would receive x from the first object, transform and immediately send the transformed value to the next

Still, I also want to use functions which are synchronous, e.g they need input to compute it's value

Now the problem is - how do I combine both with a non-trivial system? 2

In this picture, f needs x and y to compute its value, but during any point in time, only one input may be present (suppose x is present, y is not). Generalize this to higher dimensions

I can see a possible way out:

  • make x an array X = (x,y) and combine the first two objects resulting in a Type-1 like system ..

  • or make f an active object, which caches x,y and then computes

  • or make f always compute its value (e.g when only x is present, compute x*y = x*0 ...)


So the possibility of futures was mentioned. I'M using Java, so i'll frame my possible solution via Objects

X is sent, y is missing -> create future (maybe a copy of f) waiting for y X is sent, y is missing -> create future (maybe a copy of f) waiting for y

enter image description here

enter image description here

  • I am not sure I completely understand the difference between you first two solutions above. – Giorgio Jun 24 '14 at 15:23
  • in 1.), f only has one input - a vector consiting of (x,y) which will be given to the function, thus there's always everything we need to compute. in 2.) f effectivley becomes an object with ports - now it can save x (arrives first) and once y arrives, f(x,y) is computed – Jack Shade Jun 24 '14 at 16:23
  • To make sure I understand your scenario, let's check the steps in your final diagram. 1. x and y are completed in unknown order, we just know that at some point they're both ready and then 2. f(x,y) needs to be executed. 3. f(x,y) runs, completes and sends its answer over the wire. 4. What needs to be deleted? Jus the memory used by f(x,y)? Shouldn't the garbage collector do everything you need? – Patrick M Jun 24 '14 at 17:16
  • Yes indeed - the garbage collector would fitting, but I need control over the used memory (i.e. cache the "future" object). In my case, y has to be sent to the function f(x,y) directly, not the future object, and f(x,y) keeps a list of unfulfilled futures, fulfilling them as data comes in - which would be the equivalent of having two x,y "waiting" lists (if you're wondering, i'm coding a simulation :) – Jack Shade Jun 25 '14 at 19:01
8

Promises were made to solve problems like this; they work really well in functional languages (I've personally used them extensively in Javascript, where curiously jQuery actually has the worst implementation of them - see this comparison). The weird thing about using promises is accepting that things are easier when you make everything use them. I will frame my answer in JavaScript terms using the promise library Q, since that's what I know best - apologies if that's not your cup o' tea, and double apologies for answering a language agnostic question with a specific language and library to boot.

A promise is a wrapper for a callback function. In many libraries, this is called a .then() function. In simplest terms, a promise waits for a return, and then calls the function passed to then(). The simplicity was confusing for me in the beginning, so lets use your example. Let me upgrade x and y to functions. If they are functions that return promises (as many asynch heavy libraries do, like a NodeJS database accessor), this becomes trivial.

Q.all([x(), y()]).then(function(results) {
  // Do some stuff with results, which is an enumerable collection containing
  // the in-order results of the promises passed in.
});

The trouble is, what if x() and y() don't return promises? As I mentioned, your life gets easier if you make them return promises. Fortunately, any half-decent promise library provides tools for this as well. In fact, there are several ways to do this in Q.

// Upgrade x into a promise returning function
function promise_x(/*x params...*/) {
  return Q.fcall(x(/*x params...*/));
}

// Upgrade x into a promise with a deferred
// Useful if you have some standard error handling specific to x-like functions
function defer_x(/*x params...*/) {
  var deferred = Q.defer();
  // If x is asynch, assume it takes a callback function with params of (error, result)
  x(/*x params...*/), function (error, result) {
    if (error) {
      deferred.reject(new Error(error));
    } else {
      // If you have some processing to do with the result of x (and not y), you can do
      // it here and return the modified result instead
      deferred.resolve(result);
    }
  });
  return deferred.promise;
}

If you repeat that code for the y() function, you get back to where you can use Q.all() to wait for both x() and y() to complete.

Q also provides an API for creating promises, which is like a wrapper function for Q.defer().

However, the best feature a promise library provides is aggregated error handling. Expanding on our first Q.all example, what happens if x or y throw an error? As that code is written now, it gets lost. Consider the following:

MyOverallFunction() {
  Q.all([x(), y()]).then(function(results) {
    // Do some stuff with results, which is an enumerable collection containing
    // the in-order results of the promises passed in.
    var combinedResult = synchronousFunction(result[0], result[1]);
    anotherAsyncPromiseFn(combinedResult)
      .then(function(result)) {
        return result;
      });
  }).then(function(result) {
     // Here, result is the return value of anotherAsyncPromiseFn
  }).error(function(err) {
     // This is the cool part. Any error from x, y, synchronousFunction, or
     // anotherAsyncPromiseFn will propagate to here. If you don't want to
     // handle it, just re-throw it (after logging it, if you prefer).
  });
}

You'll also notice in the above example there are several places where combining sync and async methods is demonstrated.

Ultimately, the tools that are at your disposal depend upon your language and environment. Promises have been implemented in more languages than just JS, and another closely related idiom from async/functional programming is Futures. Admittedly, if your language of choice does not have a Promise library, this answer will be pretty useless to you, but hopefully you at least find it interesting.

Update: Now that you mention Java, it looks like there is a decent promise library in JDefered. I've not used it, but it looks like my examples could be applied with it in a fairly straightforward manner.

  • 1
    Great answer. There's a similar write-up at FPcomplete.com that takes a look at the issue from a Haskell point of view (while assuming the audience has no prior knowledge of Haskell) and then shows how it can be translated into C++. The gist of it is that if your language lets you succinctly write your own control structures, you can write custom control structures for async code. I didn't want to write a link-only answer so I'm glad there's a thorough answer to piggyback this on. – Doval Jun 24 '14 at 15:49
  • I was searching for an article that covers my create-a-promise-from-anything approach far more thoroughly (using Q) and couldn't find it. I was going to link it and quote the good parts; instead I ended up rewriting most of the Q.readme.MD, which is a great document, just out of order for someone who doesn't have a promise to start with. I'm glad it turned out okay, and I've bookmarked your Haskell link for later reading :-) – Patrick M Jun 24 '14 at 16:01
  • Thank you for the in-depth answer, I see what you mean - Please see my updated answer. Problem is that the active object (which is a defined formalism) really cant return anything else besides values (piecewise constant trjectories) – Jack Shade Jun 24 '14 at 17:11
  • Nitpick: the promise engine calls the function passed to then, not then itself – Caleth Aug 8 '18 at 10:45
  • That's an excellent point. Updated the answer @Caleth. Thank you. – Patrick M Aug 8 '18 at 20:50
1

What you are describing is a common issue in reactive programming. The idiom is for asynchronous code to produce a Future which acts as a rendezvous point for other code. The code that depends on the results uses call-backs (or call-forwards, depending on your point of view), which take the value as it will be produced in the future.

With suitable language support (Erlang, Scala), the use of these Futures becomes quite simple and straightforward. You might also want to investigate Actors in Akka. This provides a toolkit for much more complex systems.

  • Thank you, reactive programming was the term i was looing for. Please see my updated post. – Jack Shade Jun 24 '14 at 17:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.