staltz / cycle-onionify

MIGRATED! This was transfered to https://cycle.js.org/api/state.html
MIT License
280 stars 19 forks source link

Composability of Cycle add-ons as wrapper functions #38

Open kylecordes opened 7 years ago

kylecordes commented 7 years ago

This is not quite a bug report, more like a token of something to figure out when things are more mature.

I'm wondering about the composability of Cycle add-ons which arrive as a function which wraps the application main function. Once an application uses several of those, it needs to choose what order to wrap them. Wrapping does not seem to compose as well as adding drivers.

The specific case I hit was with onionify and cyclejs-modal:

https://github.com/cyclejs-community/cyclejs-modal

both of which arrive as a function to wrap around the application main function.

I ended up with this wrapping order:

const main = modalify(onionify(App));

... which yields both libraries working, with the caveat that onion state is not available within a component instantiated by modalify. This requires a workaround for state, carrying state needed for the modal by some means other than onion. (I briefly dabbled with a global variable, the allure of the dark side is sometimes strong...)

If I wrap them the other way, onion state is available inside the component instantiated by modalify - but modalify fails to work, for reasons I did not debug.

I would guess the fundamental challenge is: I'm using two libraries both of which expect to be the "outermost" wrapper function. Staring at the source code of both things, it's not clear to me what could be done to either to make their functionality mutually available to each other.

staltz commented 7 years ago

If I wrap them the other way, onion state is available inside the component instantiated by modalify - but modalify fails to work, for reasons I did not debug.

@kylecordes First I'd try to figure out what went wrong with that approach. Note than when doing onionify(foo), the foo is written in a way that assumes that sources.onion is available, so what onionify() is doing is just building that sources.onion and passing it to the foo. So replace foo with modalify(App) and it means modalify(App) should be written in a way that assumes sources.onion is available.

kylecordes commented 7 years ago

@staltz It seems likely I could dig through and figure it out, the source is not very long:

https://github.com/cyclejs-community/cyclejs-modal/blob/master/src/modalify.ts

... When I get a chance I will fork both, add whatever bits of debugging I need, and track it down.

More broadly, it seems like a composeability challenge. Each add-on is written and documented and tested assuming it will be the outermost wrapper. From the point of view of each, the course of action is to put it on the outside and then go debug the other add-on to figure out why it doesn't work when it's not on the outside.

Is there any way to get traction on the broader challenge? To consume a set of Cycle add-ons, and have them compose out-of-the box? It seems like trying each permutation of nesting order and debugging why a particular combination of things doesn't work together when composed in a certain order, would not scale very well to n available add-ons of which application consumes k. I've forgotten the exact formula for permutations P(n,k), but it involves factorials, and gets ugly fast.

Maybe it will turn out that there is some set of rules and way of testing for those rules, that a Cycle add-on could follow if it needs to ship as a wrapper function, such that it can be expected to compose in a predictable way with other wrapper functions?

jvanbruegge commented 7 years ago

You basicly have to compose them based on the features you want where. If you want state in a modal, you would have to put the state around the modal. I have no idea why this dies not wirk, please open an issue for that.