salsita / prism

React / Redux action composition made simple http://salsita.github.io/prism/
496 stars 24 forks source link

what are some of the "benefits" from Elm that you can't replicate? #1

Closed faceyspacey closed 8 years ago

faceyspacey commented 8 years ago

...i really like it by the way? ..Also why are generators needed?

tomkis commented 8 years ago

There are two benefits: 1) Elm provides type safety so you'll get much more compiler information when something doesn't work - it works especially well with the Elm Architecture 2) There are no runtime errors in Elm

Generators are not necessary unless you need side effects and even simple applications need side effects, for example API communication, logging, caching etc. https://github.com/salsita/redux-side-effects - This package allows us to reduce side effects using generators... Basically any function yielded within reducer is not immediately executed, instead its execution is deferred and dispatch is provided -> this is basically same approach which Elm is using, but Elm is using declarative effects which are reduced as well, therefore Elm's updater returns Pair<Model, Effects> and we emulate the same by using yield for emitting thunks (side effects) and the returned value is just plain old mutated Model.

anler commented 8 years ago

@faceyspacey maybe you'll like olmo though I need to port all the examples

minedeljkovic commented 8 years ago

To achieve a variation of benefit 1), in our project, we use excelent tcomb library, similar to how it is done in tcomb-redux. It follows composition of state and actions nicely. @tomkis1, I can do a PR here, if you are interested to see how it fits with examples.

We started the project about two months ago, and came up with almost identical redux aditions as you implemented here (forwardTo, mapEffects), which is no coincidence since we also wanted to port critical parts of Elm architecture to Redux and are using your redux-side-effects. :) So far we are very satisfied how it grows around such architecture.

tomkis commented 8 years ago

@minedeljkovic I am really glad that we are sharing the view.

To achieve a variation of benefit 1), in our project, we use excelent tcomb library, similar to how it is done in tcomb-redux. It follows composition of state and actions nicely. @tomkis1, I can do a PR here, if you are interested to see how it fits with examples.

I was something about something less intrusive Flow? TypeScript? Yet I am more inclined to Flow as it's babel friendly.

We started the project about two months ago, and came up with almost identical redux aditions as you implemented here (forwardTo, mapEffects), which is no coincidence since we also wanted to port critical parts of Elm architecture to Redux and are using your redux-side-effects. :) So far we are very satisfied how it grows around such architecture.

Frankly, we are getting more and more skeptical about Generators as using them has its drawbacks. You can't use callbacks because yield* is not automatically propagated which is a showstopper for example when using something like immutable.js.

So my long term idea is think about Elmish composability of sagas using https://github.com/salsita/redux-saga-rxjs and avoid side effects in reducers at all. Also, I would rather avoid nesting actions. Instead, the composition would be done via action naming so that we can do easy pattern matching. Action would be something like

{
  type: 'COUNTERS.TOP.INCREMENT',
  payload: 1
}
tomsdev commented 8 years ago

@tomkis1 could you explain why pattern matching on the action name is better than nesting actions in your opinion? I wonder if there is a difference in performance also when you want to check for a "match".

tomkis commented 8 years ago

@tomsdev it's possible to use regexp for pattern matching with plain old strings so it allows us more advanced matching. Also, unwrapping is easy because there's no need to recursively access the action.payload.payload.payload...

namjul commented 8 years ago

@tomkis1 in which way do you think immutable.js is incompatible with redux-side-effects? can you give an example of a situation in which i need to yield inside a immutable.js callback.

tomkis commented 8 years ago

@namjul imagine following:

function* updater(immutableAppState) {
   return immutableAppState.map(item => {
       yield ApiSideEffect();

       return item.set('loading', true);
   });
}

This simply doesn't work and it doesn't work with anonymous generator as well.

function* updater(immutableAppState) {
   return yield* immutableAppState.map(function*(item) {
       yield ApiSideEffect();

       return item.set('loading', true);
   });
}

does not work either.

namjul commented 8 years ago

right thx. but wouldn't the following work:

function* item(item) {
       yield ApiSideEffect();

       return item.set('loading', true);
}

function* update(immutableAppState = Immutable.Map(), action) {
        return immutableAppState.set(action.id, yield* item(state.get(action.id), action))
}

probably there are still situations you need an yield directly in the map callback.

In your next branch i saw that you couple redux-elm with redux-side-effects. What i would like is to use your redux-elm action-pattern-routing solution + redux-loop to solve the problem with immutable.js. Is there a way we can use these seperatly?

namjul commented 8 years ago

so i tried it and build the randomGifList with immutable.js without using map:

.case(name, function*(state, action, randomGifId) {
    return state.setIn(['gifList', randomGifId], yield* mapEffects(randomGif.reducer(state.getIn(['gifList', randomGifId]), action), name, randomGifId))
}, Matchers.parameterizedMatcher)
tomkis commented 8 years ago

@namjul

but wouldn't the following work:

function* item(item) {
       yield ApiSideEffect();

       return item.set('loading', true);
}

function* update(immutableAppState = Immutable.Map(), action) {
        return immutableAppState.set(action.id, yield* item(state.get(action.id), action))
}

It would not, because your item function is generator and calling generator returns iterable therefore it would only set iterable into the App state.

probably there are still situations you need an yield directly in the map callback.

There's (or should be) a workaround by allowing yielding array of effects, that way you could map effects using traditional map function and then yield the result.

In your next branch i saw that you couple redux-elm with redux-side-effects. What i would like is to use your redux-elm action-pattern-routing solution + redux-loop to solve the problem with immutable.js. Is there a way we can use these seperatly?

For now, it looks like we will lock this repo with Generators because redux-loop seemed too awkward for us to use, there's an alternative project which is doing that though https://github.com/jarvisaoieong/redux-architecture

so i tried it and build the randomGifList with immutable.js without using map:

Yes that's definitely a solution, however you have to realize that with redux-elm you will most like won't even need immutable js because entire application state is nicely sliced into smaller chunks and spread operator serves pretty well here.

namjul commented 8 years ago

yes, i can see that immutable.js is likely not needed with redux-elm. i am still confused why the first example would not work. Its similar to the second example and it works. yield* would just return the return statement from the item generator function. Which would be an immutable.js object...

function* anotherGenerator(i) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
  return 2;
}
function* generator(i){
  yield i;
  console.log(yield* anotherGenerator(i));
  yield i + 10;
}
var gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
// 2
console.log(gen.next().value); // 20

it just returns the return value not an iterable. i probably miss something.

So my long term idea is think about Elmish composability of sagas using https://github.com/salsita/redux-saga-rxjs and avoid side effects in reducers at all.

to you mind to show me how this would look like? if i define the fetchGif effect in randomGif module how can i reuse/compose it in randomGifList with redux-saga-rxjs. or is this ATM not possible.

tomkis commented 8 years ago

it just returns the return value not an iterable. i probably miss something.

No you are not, sorry my bad, I overlooked the yield* keyword. You'r right that will work.

to you mind to show me how this would look like? if i define the fetchGif effect in randomGif module how can i reuse/compose it in randomGifList with redux-saga-rxjs. or is this ATM not possible.

You can actually check history of the repo https://github.com/salsita/redux-elm/blob/v0.3.0/examples/src/6-list-of-random-gif-viewers/main.js#L58 the composeSaga function does the trick, though the implementation is wrong and does not work for all use cases.

Saga composition is indeed really hard to achieve but doable. Anyway, we've decided to go the Generators way, at least for 1.x

tomkis commented 8 years ago

Closing this issue in favour of https://github.com/salsita/redux-elm/issues/5