JavaScript Getter-Setter Pyramid
staltz.com
staltz.com
I did find it confusing at first, though. Maybe it could have used a disclaimer, or maybe the author should have chosen different terms (maybe "producer" instead of "getter" and "consumer" instead of "setter"?). I also find it unintuitive to call console.log a "setter", and "consumer" feels better to me.
It took a bit of adjusting, but local terminology is a lot more readable than if everybody tried to use universally-unambiguous language.
[It's a term established in the language specification.]
I'd say JS getters/setters are syntactic sugar for the OOP concept the author is talking about.
[Edit] Correction: should have been, at least since ECMA-262 5th Ed.
That all said, within the model being established by the author, everything does make sense, eventually, and the difference between IO output functions and setters, for the sake of the abstractions that the author builds, is non-existent.
I find the getter/setter nomenclature a little imprecise. It makes me think of interacting with fixed state, rather than input and output into completely abstract "systems" on the other side of the function calls. To me, provider/consumer seems a bit more on-the-mark.
It seems to me that there are other paths of generalization you could follow, as well (e.g. https://leanpub.com/combinators). Even though we all know that you can model...well...anything with functions, it's still enlightening and mindbending to explore the patterns for actually doing so.
let usersRange = (start, end, result, doneCb, errorCb) => {
let xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open('GET', 'http://jsonplaceholder.typicode.com/users/' + start)
xhr.send();
xhr.onerror = ev => errorCb();
xhr.onload = ev => {
result.push(xhr.response);
if (start < end)
return usersRange(start + 1, end, result, doneCb, errorCb);
done(result);
}
};
usersRange(0, 10, [], /* completion callbacks */);
Simpler, not really harder to read (half of it is just boilerplate around XHR setup, which is abstractable away). The meat of it is onload handler. const usersRange = async (start, end) => {
var result = [];
for (let current = start; current < end; current++) {
result.push(await fetch(`http://jsonplaceholder.typicode.com/users/${current}`).then(r => r.json())
}
return result;
} let usersRange = (start, end, result, done) => {
if (start == end)
return done(null, result);
GET('http://jsonplaceholder.typicode.com/users/' + start,
res => usersRange(start + 1, end, (result.push(res), result), done),
err => done(err)
)
};
GET() just wraps the XHR code in my original post and provides access to onload/onerror callbacks. I used XHR, to avoid using fetch/Promise, which is part of the pyramid.I mean, I prefer async/await. The point is that it's just not inherently that much more verbose to write callback based async code. Composition would be more complicated. You'd have to use some abstract helper methods, or nest the callbacks. For example:
chain(
(_, next) => usersRange(0, 10, [], next),
([err, res], next) => {
if (err)
// blabla
usersDoSomething(res),
}
)
vs: try {
let res = await usersRange(0, 10),
usersDoSomething(res),
} catch(ex) {
if (err)
// bla bla
}
You can do some pretty fun stuff with functions, extending JS syntax so it has simulated goto for example: let users = [], nextUser = 0;
labelledChain(
(_, next) => GET('/users', next),
'got-users', ([err, res], next) => {
users = res;
next();
},
'get-next-user', ([err, res], next) => {
if (users[nextUser])
GET('/users/' + users[nextUser].id, next);
else
doneCallback(null, users);
},
'got-next-user', ([err, res], next) => {
users[nextUser] = res;
nextUser++;
next.goto('get-next-user');
},
)
Fun challenge implementing labelledChain so that this all works. :DJavaScript has goto, it's the `continue` statement[1].
1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
I have to admit, I didn't like promises and preferred node style callbacks until async/await and have been using it with babel since before the name change.
return nextUser(...
be return usersRange(... ? function getGetNext() {
let i = 2;
return function getNext() {
const next = i;
i = i * 2;
return next;
}
}
As a lisp coder [and not a JS coder], I'm wondering: why does the function getNext() have to be named? Why not just say something like: function getGetNext() {
let i = 2;
return function() {
const next = i;
i = i * 2;
return next;
}
} return () => {
const next = i;
i = i * 2;
return next;
}
But in this case the two are equivalent. function * getNext(){
let i = 2;
while(true){
i = i * 2;
yield i;
}
}
Here, no closure needed.https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guid...
If there is more than one Observer of the same AsyncObservable, is there a separate queue for each of them?
I'm thinking of AsyncSink[1] used by AsyncIterableX[2] (both of IxJS[3]) and how they might be conceptually related to implementations of AsyncObservable.
[1] https://github.com/ReactiveX/IxJS/blob/master/src/asyncsink....
[2] https://github.com/ReactiveX/IxJS/blob/master/src/asyncitera...
For the first part I didn't like the article since it's somewhere between fp and oop, vague or foggy. But it's just my interpretation based/biased with previous knowledge.
Once I assumed the article is conceptual and in harmony with it's definitions, everything got much more sense.
I like it.
IE: Walking into an old antique store and attempting to explain the genius logic behind it's organization. Shortly after the owner comes out and says, "No, I just throw shit wherever."
JavaScript is specified in terms of objects, has richer facilities for dealing with objects than functions, functions are objects but not vice-versa. So, objects are the cornerstone. Callers are free to ignore a function's arity even.
There's no type checking or syntactic sugar for dealing with first-class functions, so complex functional programming leads to brutal stack traces and if you ask a debugger what any part of this "pyramid" is, it tells you, "well, it's a function."
If I'm using a parser combinator in Haskell, for instance, I can just say:
liftA2 (+) parseInteger parseInteger
It's pretty clear (if you're familiar with functors) that I'm parsing two integers and adding the result. And it has to typecheck, so 90% of my stupid mistakes are caught by the compiler.But, also, if I go into ghci, I can see what it is:
:t liftA2
Applicative f => (a -> b -> c) -> f a -> f b -> f c
So I can see the first argument (a -> b -> c) is a function with two arguments, and the second, f a, and third, f b, are boxed in the Applicative, and the result, f c, is boxed in the Applicative. That's how you can understanding that it's combining two parsers using + and then the result is itself a parser.Javascript, even though it supposedly has all its type information at runtime, really doesn't know any of this because a function is a function and that's all it knows. You could replicate liftA2 in Javascript, but it would be entirely mysterious what's going on.
I remember this one, from the Uphill Conference: https://www.youtube.com/watch?v=fdol03pcvMA