Live data from Hacker News

Callbacks are imperative, promises are functional

blog.jcoglan.com

131–140 of 154 posts

Re: Callbacks are imperative, promises are functional

#131

Earlier quoted context omitted.

You're defining a promise in terms of what is "under the hood." You actually define promise in terms of what-the-hardware-does. This demonstrates the varying levels of abstraction that we're operating at. You define promise: >A "promise" is a delayed computation. It's a stand-in for a value, and the computation referencing it will suspend its execution until a value is available for it to consume. I define promise: >…

> When I want to learn about the distinction between declarative paradigms and functional paradigms I talk to people who specialize in drawing that distinction. Since the blog post is prima facie drawing a distinction between functional and imperative, the value I'm looking for is an analytically rigorous distinction between abstract concepts. Who specializes in making that distinction? Point me in the right directio…

Well, you're right that I'm an asshole. And you're right that I'm not inline with your discipline.

I'm just inline with my own discipline where promise has a different meaning, a super ordinate meaning that includes your meaning. As a consequence, anyone from your discipline just thinks I'm clueless. I'm definitely not going to convince you that the typical definition of promise, the one you're used to, is actually just an operational definition--an instrumental label assigned to a particular instance of a promise-like thing.

I'd cite the tiny field of cranks who think like me, but that would just bring shame on them by association. I'm not doing research in functional programming. I'm working on hair-splitting tyrannical distinctions-without-a-difference. According to your paradigm, I'm an outright fraud spouting bullshit. So enjoy your victory I guess.

Re: Callbacks are imperative, promises are functional

#132
post #102
post #57

This code doesn't look right to me: // list :: [Promise a] -> Promise [a] var list = function(promises) { var listPromise = new Promise(); for (var k in listPromise) promises[k] = listPromise[k]; Perhaps the assignment is supposed to be the other way around? for (var k in promises) listPromise[k] = promises[k];

I asked the same question in Twitter. Turns out James was actually augmenting (i.e. modifying) the array object `promises` to behave as a promise itself. I don't think this was a particularly beautiful way of doing it but it seems to work now that I think of it. Promise libraries, like RSVP.js [1] he referred to, typically implement a way to construct a promise with a depends-on-many relationship, as a function possi…

A day later I looked at this again and I'm a little closer to understanding.

      var listPromise = new Promise();
creates an object that, being a Promise object, has certain methods and internal state, derived from the prototype of Promise.

      for (var k in listPromise) promises[k] = listPromise[k];
This confused me because I thought "k" was a stand-in for a numeric index, e.g. that it was doing promises[0] = listPromise[0], promises[1] = listPromise[1], etc. That is not what's going on. Rather, "k" refers to attributes and/or methods that objects of the Promise class have by default. It's copying those onto `promises` — the array `promises` itself, not the individual items `promises[i]`, which keep their existing methods and attributes.

Coming from a Python background, I think I would have found this more obvious if the variable "k" were instead called "method" or "attr". If it was `for (var method in listPromise)` it'd be much clearer what's going on, whereas single-letter variables like i, j, and k are, to me, stand-ins for integers.

It was also confusing, as you said, that the function uses destructive update rather than treating the input as a value. James did mention this ("augmenting the list with promise methods"), but it's still unexpected, especially when the function is preceded by a Haskell type signature.

The reason I only say I'm closer to understanding, and not quite there yet, is I'm not sure what it means to do `new Promise()` or what is being copied over in the above for-loop. I tried James's code with a Promises/A+ implementation, rsvp.js (https://github.com/tildeio/rsvp.js), but it won't let me do `new Promise()` because it works differently:

    > var promise = new RSVP.Promise();
    TypeError: You must pass a resolver function as the sole argument to the promise constructor
Per an example in RSVP.js's readme, it's expecting this:

    var promise = new RSVP.Promise(function(resolve, reject){
        // set up a callback that calls either resolve(...)
        // or reject(...)
    });
If James is using a specific promises implementation in his code, it appears to be the one he defined in a past blog post (http://blog.jcoglan.com/2011/03/11/promises-are-the-monad-of...), which in turn builds on a module from his JS.Class library (http://jsclass.jcoglan.com/deferrable.html), which I hadn't heard of before.

I still think this is a great article, but that code snippet has proven to be quite a puzzle.

Re: Callbacks are imperative, promises are functional

#133

Earlier quoted context omitted.

In other words, promises allow us to separate concerns. Document retrieval is one concern, collation another. Other programming languages have this too. They're called a 'METHOD'. Sorry, couldn't resist. On a serious note, look at your code in here: https://github.com/fruchtose/muxamp/blob/master/lib/playlist... And look at your 'playlistCount' function on line 39 (which for no apparent reason you've made a variable)…

Having now thought about it a bit more, you could actually write the code so you don't have to baby the promise at all in normal code. So the promises didn't increase the complexity of the code really, the lack of abstraction is. I'm thinking of something like the below as a dbHelper class. Note I'm passing the error messages into the deferred reject method rather than using console logging. I'm not sure if the q API…

First of all, thank you for taking the time to read over my code. I don't get enough of this.

You're right that 28 lines is pretty ridiculous, but it's because I never abstracted out that code. The playlist code is some of the ugliest in that project, because I got pretty lazy with it. I know it's terrible. The console logging stuff is part of that.

Q actually allows exceptions to cause promise rejections, which is both a nifty feature and a potential curse (e.g. throwing an exception before releasing a resource).

I like the changes you propose (not considering testing), but with slightly different implementation. the DbCommand should be creating its own deferreds, rather than accepting one as a parameter. This kind of promise handling is best left up to DbCommand to implement, rather than the caller. In Q it's easy to chain promises, like so:

    Q.fcall(someFunction).then(function(result) {
      return functionThatReturnsAPromise();
    }).then(function(secondResult) {
      console.log(secondResult);
    }).done();
Also, in a proper redesign, Q's denodeify function can change the whole flow of the execute function entirely:

    DbCommand.prototype.execute = function(query, onComplete) {
      var self = this;
      Q.denodeify(dbConnectionPool.acquire).then(function(connection) {
        self.connection = connection;
        return Q.denodeify(connection.query);
      }).then(function(rows) {
        return onComplete(rows);
      }).finally(function() {
        self.connection && dbConnectionPool.release(self.connection);
      }).done();
    };
Q's denodeify call works with Node.js callbacks which follow the convention that the error is the first argument, and the result all others. denodeify then converts the error into an argument for any calls to Q.fail. Any uncaught errors will be thrown after done() is called.

However, I am not sold on the idea of creating a prototype for DB commands. There's no state that needs to be held, and the code is abstract enough without introducing a prototype.

Again, thanks for the code review. The playlist DB code needs a lot of refactoring, since right now there's too much repetition I've been too lazy to fix. If you want to talk some more, feel free to email me.

Re: Callbacks are imperative, promises are functional

#134

Not to focus too myopically on the given example, but I can’t help but wonder why it’s a requirement that the first file be handled specially? A less contrived example would make the argument more convincing. If I wanted to compute the size of one file relative to a set, I’d probably do something like this: queue() .defer(fs.stat, "file1.txt") .defer(fs.stat, "file2.txt") .defer(fs.stat, "file3.txt") .awaitAll(functi…

These kinds problems can easily be solved with promises too. It would be even simpler if `fs.stat` returned a promise and there are promise libraries that do that. Promises is a small library I use, probably about the same number of bytes as your library, as I transition my code from callbacks to promises.

      var queue = new Promises;
      fs.stat("file1.txt", queue.cb());
      fs.stat("file2.txt", queue.cb());
      fs.stat("file2.txt", queue.cb());
      queue.all()
        .then()
        .fail();
But, comparing how promises solves the same flow as callbacks misses the point. Here's an example where an action is taken when two events fire (promises shine here):

      pub.on('foo', function() {
        promise1.fulfill();
      });
      pub.on('bar', function() {
        promise2.fullfill();
      });
      Vow.all([promise1, promise2]).then(...).fail(...);

Re: Callbacks are imperative, promises are functional

#135
post #98

Earlier quoted context omitted.

Just plain-old native functions - that's the whole point.

when you put "plain old native functions" in an array, with the intent of executing them in sequence, with the output of i being fed into the input of i+1, congratulations, the functions are now implicitly promises. Because, in the end, what, semantically, is the difference between: runqueue([func1,func2,func3,func4]); and func1().then(func2).then(func3).then(func4); No significant difference at all, really. except t…

The difference is that the first works with all of the native list functions, as well as all of those in e.g., underscore, without any extra work. The latter doesn't. Now, the latter certainly offers some other features, but my point was that, in specifically building an HTTP server, it's been my experience that those features aren't of as much use as being able to use the native list functions to, say, map a log function onto the list of functions, or reduce while halting execution under particular conditions.

Re: Callbacks are imperative, promises are functional

#136
post #114

Earlier quoted context omitted.

Please use more readable names in your code. Use of names like 'fs' in key places makes it unreadable.

The convention for Haskell is to keep the active scope of variables very small. Any variable with an active scope of larger than maybe 3 lines, I make longer. Since these examples were hardly longer than that, I feel quite justified with short names. In Haskell, if you see a short name, look up and down 3 lines for the definition. If you can't find it then complain.

I think you are right, and it is a convention in the functional world. People are using (and worse, reusing in a close proximity!) meaningless names like that. And I think these people have zero regard to anyone who is reading their code.

Well... more power to python, and culture that embraces 'what your see is what your get' and super-readable code.

Re: Callbacks are imperative, promises are functional

#137
post #114

Earlier quoted context omitted.

The convention for Haskell is to keep the active scope of variables very small. Any variable with an active scope of larger than maybe 3 lines, I make longer. Since these examples were hardly longer than that, I feel quite justified with short names. In Haskell, if you see a short name, look up and down 3 lines for the definition. If you can't find it then complain.

I think you are right, and it is a convention in the functional world. People are using (and worse, reusing in a close proximity!) meaningless names like that. And I think these people have zero regard to anyone who is reading their code. Well... more power to python, and culture that embraces 'what your see is what your get' and super-readable code.

There is actually an interesting technical reason for having short names in generic Haskell functions. Because of parametricity, the behavior of the function doesn't depend on what the values actually are. The shortness of the names really is meant to convey "don't think about what this is doing, because it's not important for this function". In the traditional example for map,

  map f [] = []
  map f (x:xs) = f x : map f xs
You're supposed to infer from the short function names that f and x could be anything. The only important bit is that you can apply one argument to f (so, for example, f could take two parameters, and then map is just doing a single partial application). In that context, x and xs is actually a better convention than "first" and "rest", because they indicate the adherence to the type system. The naming here is saying that x is of the type of elements of xs, and that this is the only important information for map. This seriously helps in more complicated functions like zip, etc.

Re: Callbacks are imperative, promises are functional

#138

Earlier quoted context omitted.

I think you are right, and it is a convention in the functional world. People are using (and worse, reusing in a close proximity!) meaningless names like that. And I think these people have zero regard to anyone who is reading their code. Well... more power to python, and culture that embraces 'what your see is what your get' and super-readable code.

There is actually an interesting technical reason for having short names in generic Haskell functions. Because of parametricity, the behavior of the function doesn't depend on what the values actually are. The shortness of the names really is meant to convey "don't think about what this is doing, because it's not important for this function". In the traditional example for map, map f [] = [] map f (x:xs) = f x : map…

I'm not so sure that briefness and adherence to that convention improves readability. Of course f, x, xs is much much better than 'first' and 'rest', or 'a', 'b', 'c', but something like 'func' and 'iterable' gives more context. And frees one's attention to more important things, than looking up and down the code.

Compare:

    map f [] = []
    map f (x:xs) = f x : map f xs
With:

    map f xs = [f x | x 
Or even better, in Python:

    map = lambda func, iterable: [func(x) for x in iterable]
Which one is more readable?

First one requires looking up and down in order to understand what is going on. Second one is better, context is limited to one line. And the last one doesn't require you to remember context at all.

Re: Callbacks are imperative, promises are functional

#139
post #114

Earlier quoted context omitted.

The convention for Haskell is to keep the active scope of variables very small. Any variable with an active scope of larger than maybe 3 lines, I make longer. Since these examples were hardly longer than that, I feel quite justified with short names. In Haskell, if you see a short name, look up and down 3 lines for the definition. If you can't find it then complain.

I think you are right, and it is a convention in the functional world. People are using (and worse, reusing in a close proximity!) meaningless names like that. And I think these people have zero regard to anyone who is reading their code. Well... more power to python, and culture that embraces 'what your see is what your get' and super-readable code.

We'll have to agree to disagree. I think long variable names for short-lived variables decreases readability. Oftentimes these "points" are just used to glue functional pipelines together and have little-to-no intrinsic meaning. The true documentation comes from the types and is thus more trustworthy.

Re: Callbacks are imperative, promises are functional

#140
post #112

Earlier quoted context omitted.

For people that like math, sure, why not. When implementing mathematical concepts, if you squint at Haskell code you can see the original formulas, which should make it easier for people used to this way of thinking. EDIT: I'm not implying it's useful just for programming "math stuff", after all, everything can be reduced to a mathematical problem - including game engines[1], web application frameworks[2], etc. [1] h…

And it's probably one of the most significant things limiting adoption of Haskell.

Exactly. From my point of view, Haskell is the perfect language which unfortunately comes with the worst naming conventions. (I generally develop in C#, F# and JavaScript)
Post reply on HN