5 ms·
Composability: From Callbacks to Categories in ES6
- nailer 10y agoNote his call back hell is exacerbated by the unnecessary 'else' after checking for errors and returning. I'm not sure that .bindTo() chaining reads any better than .then() chaining or async.waterfall().
- homami 10y agoWe have to handle errors at least one time during the operation. Both Promise and Callback classes propagate error in the computation and delay error handling till the last step (usage). I agree that bindTo is not much different from then, but IMHO pipe and pipeTo are.
- JasonSage 10y agoThe point isn't really to argue for another naming scheme for Promises. The construction of the Callback class is used to build up the reader's understanding the Monadic structure before introducing Categories and Kleisli Categories. The comparison of the structure of these abstractions comes up several times. I think the final example (of myLongOperation) reads extremely well, and is pretty minimal, as the author states.
- lmeyerov 10y agoInteresting: as far as I can, this is promises, except there is a final strictness point at the end of the composition. That is, in turn, analogous to a one-shot form of old FRP formulations, where there is a final strictness/execute call. We already see that in JS libraries, like 'subscribe' in Rx: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/subscribe.md https://github.com/Reactive-Extensions/RxJS/blob/master/doc/... FWIW, I'm actually not that thrilled about this sort of deferred approach. In Rx, that is part of the reason 'hot' vs 'cold' documentation has to exist, and I've seen many people struggle with it in theory & practice.
- noelwelsh 10y agoIIUC, it's a bit different. Kleislis reify functions with type (A => F[B]), providing composition of these functions. (See http://underscore.io/blog/posts/2015/10/14/reification.html http://underscore.io/blog/posts/2015/10/14/reification.html) Composition of Kleislis is like old style FRP in that it is synchronous. However the hot/cold distinction in Rx is because execution is not deferred, as I understand it. IIUC observables / streams / whatever they are called in Rx start running as soon as they are defined. If `naturalNumbers` is the event stream of natural numbers, the result of naturalNumbers.map(x => print(x)) might not output 0, 1, ..., because a whole bunch of natural numbers might have already been emitted before `map` is called. It is this difficulty with reasoning that motivates hold/cold streams (again, as I understand it). A simple solution is to separate defining the network and running it. e.g. by requiring calling `run` method to get a result. Then substitution is maintained in the "world" prior to calling `run`, and this distinction is unnecessary.
- WorldMaker 10y agoThe hot/cold distinction is indeed because most Rx Observables use deferred creation (cold) and most Rx Operators use deferred creation (cold) until the equivalent to a run is called (subscribe) making the Observable hot. The complications to this that make for so much documentation come from the places where those "mostly" answers are wrong: Rx has ways to build hot Observables that are not deferred and begin immediately; Rx has a few rare exception Operators that can force an Observable hot, those confusing matters; finally, Rx cold Observables by default don't share deferred execution so multiple subscriptions create multiple "heat transition" side effects (ie, an observable is "run" every time it is subscribed). There are mechanics and operators in place to deal with all of these cases in Rx, but that adds to the learning curve of knowing when deferred execution takes place. (So yes the separation you describe exists between creating a network of cold observable and making them all "hot", but it does bring its own complications only furthering the need for the hot/cold distinction in cases where you are trying to avoid repeating things like side effects, which will happen when you aren't properly sharing the same "hot" source.) So yes, Hot/Cold is entirely about deferred versus immediate observables/streams and both the surprises of finding a hot observable when you expected a cold one (as in the case of your example) or even a cold observable when you expected a hot one (side effects and memory leaks and "no execution" problems because you forgot to make anything hot).
- girvo 10y agoAs you start to look at these concepts, and Ramda, I highly recommend playing with ramda-fantasy[0] and the fantasy-land[1] spec that it implements. Super useful, and really made this sort of functional programming "click" for me (and, has really cleaned up some abstractions in a couple production projects, too!) [0] https://github.com/ramda/ramda-fantasy https://github.com/ramda/ramda-fantasy [1] https://github.com/fantasyland/fantasy-land https://github.com/fantasyland/fantasy-land
- spraak 10y agoHere[0] is a Node REPL with rambda and rambda-fantasy functions as globals. It feels so fun to use, like a whole other language. E.g. `map(x => x*2, [1, 2, 3])` (no need to write `R.map`) [0] https://github.com/hemanth/ramda-repl https://github.com/hemanth/ramda-repl
- shakiba 10y agoLooks like a new hell!
- kccqzy 10y agoHow is this a new hell? This is some elegant, mathematics-derived abstraction.
- pron 10y agoElegance is in the eye of the beholder, and being mathematics-derived says nothing of its suitability for direct use by humans. Gödel numbering is also "mathematics-derived" but few people would regard it as particularly elegant or useful (it may be elegant in the mathematical purpose it serves, but not for engineering).
- scribu 10y agoIt will turn into a hell because JavaScript lacks Haskell's strong type system. The tooling will not give you early warning if you pass a plain function into a construct that expects a promise etc.
- homami 10y agoThat's very true. But on the other hand dynamic typing is what enables us to play with JavaScript and re-create many concepts from FP in plain JS. This is not easy in many strongly typed languages which their type system falls short of Haskell's (that supports ADTs, kinds, recursive types, etc.).
- scribu 10y agoYes, dynamic typing allows experimentation. But just because you can make JS look like Haskell doesn't mean it's the most effective way to get stuff done. You'd be better off writing Haskell and transpiling it, IMO. Note that I'm talking about writing an entire application in this style, not just sprinkling categories here and there.
- 10y ago
- spraak 10y agoReally fun read!
- johnbm 10y agoFor the love of God, stop using arithmetic to demonstrate functional concepts. It makes your technique look contrived and unnecessary, and fails to illustrate how to actually use it to simplify real async operations.
- dozzie 10y agoArent you guys around JavaScript tired of changing your programming paradigm every half a year?
- BinaryIdiot 10y agoWhy would anyone made such a change every year? Many people write articles about better ways to do X in many languages but that doesn't mean you change everything to follow that today. Maybe it informs your decisions tomorrow, maybe it doesn't. Ultimately you don't need to do anything here. Personally I don't use promises and won't use this either but that's only because I am working on a code base that needs to work in more places (so no ES6 for me and no I don't need the extra complexity in my build and debugging steps to do a transpile).
- homami 10y agoI'd love to know which runtimes do you have to support which don't support ES6 if you don't mind? I imagine you're dealing with old browsers.
- BinaryIdiot 10y agoAt my previous job it was older browsers but my primary avoidance of ES6 is related to my open source project (msngr.js) where I want to ensure I support not only older browsers but also node.js 0.10. I could use ES6 and transpile with babel but I try not to do that when possible to keep the build process as simple as possible. If there was a specific ES6 feature that I think would hugely benefit my productivity or if I was working on a project that could utilize ES6 in other ways I would certainly use it more.
- joostdevries 10y agoI was recently thinking while working with ES6 Promises that I liked to have something equivalent to Haskells `do` notation. Or Scalas `for` comprehension. Does something like that exist? If not maybe I should try Kleislis instead. But I'd like my code to be accessible for my colleagues. And it's my rule of thumb that I try not to use point free constructions (like Kleislis) because a lot of colleagues will no longer understand my code easily.
- joostdevries 10y agoOn second thought: I guess Async Await are like a `do` notation specifically for Promises...
- homami 10y agoThat's true. await sequence actions much like do notation.
- joostdevries 10y ago..and it's flat instead of nesting.
- jacinabox 10y agoThe article didn't really sell that arrows have an advantage over monads as far as callbacks are concerned.
- homami 10y agoDo you have any particular use-case in mind for arrows in JS?