Avaq on `how to spread data across multiple chaining` - lambdaydoty/monadic-js GitHub Wiki
Skandrovic @Skandrovic Dec 15 20:57
Hi guys, I'm new to Fluture and Functional programming in general. How yould you spread data accross multiple chaining.
Future.resolve(1)
.pipe(Future.chain(Future1))
.pipe(Future.chain(Future2))
.pipe(Future.chain(Future3))
Whats is the best way in your opinion to pass data from Future1 to Future3 ? Do you think that using side effects or returning multiple values is a good idea ?
Sources :
- https://2ality.com/2017/08/promise-callback-data-flow.html
- https://glebbahmutov.com/blog/passing-more-than-single-value-through-promise-chain/
Aldwin Vlasblom @Avaq Dec 16 05:33
Hi @Skandrovic, and welcome! Good question. I'll give a full answer, with some terms you may not be familiar with. Just skim over them, I think the general idea should still land.
I think using side-effects for anything except performance optimization is a bad idea. Side effects such as the ones demonstrated in the linked article increase the complexity of your code in ways that make the code quickly unmaintainable. In the functional programming ideology, we keep side effects to a minimum, and strictly and formally control necessary side effects. That puts the side effect option at the bottom of my list.
The most straight-forward option is using nested scopes, which has also been given in the article. The benefit is that it's easy to achieve, the drawback is that it ties the nested code into its context. When the nested scopes option suits your needs, you may choose to use a coroutine (also known as "do-notation" or async/await). Fluture lets you write coroutines with go. The benefit of coroutines over nested scopes is that it's easier to write and read, and the drawback is that it relies on syntax, making it a bit harder to see patterns, extract code, etc.
The other option, to use multiple values, can be useful at times. The benefit is that your code is not strictly tied to a context any longer, but the drawback, I find, is that it still implicitly ties your function to that same context because the function types have to adjust to your multiple-values-wrapper, in a sense encoding knowledge about the context. But when writing code that should be a bit more formal, for example a library, it can be very useful. In functional programming, we have actually some formal datatypes to represent "multiple values" at our disposal. For example, you could use a Pair to simply pair up two values (eg. [a, b]
), you could use the Reader Monad to carry multiple values when one of the values will only be known at a later stage in the pipeline, or you could use the State Monad to have one of the values act as mutable state.
The State Monad also brings us full-circle, because it provides a 1-to-1 replacement for the "side effects" solution, where instead of using a mutable variable, you use a monadic abstraction to act as a mutable variable. I rarely find use for this in JavaScript however, although I did create an experimental library that uses this principle.
In summary:
- For most cases, nested scoping / coroutines is the solution with the best trade-offs in my opinion.
- For more formal code using one of the available multiple-value-wrapper types can be a good option.
- I recommend not relying on side-effects.