Is the view not aware of streams?
dmitriz opened this issue · 5 comments
Love the introduction here
https://github.com/foxdonut/meiosis/wiki/The-Fundamental-Setup
but found this confusing:
There is just one source stream: update. The view code does not create additional streams. In fact, the view code is not aware of streams at all; views just call the update as a function that was passed as a callback.
When view
is called, it is passed the update stream instead of function:
const update = flyd.stream();
...
models.map(model => ReactDOM.render(view(model, update), element));
The only reason it works here is because of the flyd
api treating streams as functions. But you need to know this in order to write the view correctly, and it would not work when you replace flyd
by another stream library with different api. E.g. if update(val)
were replaced by update.emit(val)
, then the view
must be aware of it.
What seem conceptually happen here is some sort of lifting where the update
was previously defined for functions, but when called, is actually lifted to stream values.
I should add, because stream is passed in place of function update
, I find this a bit difficult to unravel:
const increase = () => update(model => {
model.value = model.value + 1;
return model;
Here we are passing the anonymous function to the update
stream? The way I understand the flyd
api, is that you can pass directly the value:
update(model.value + 1)
Would it do the same? Any reason to pass the function instead?
Right now the anonymous function is mutating the model
object passed to it as argument, so it is not pure. Is it intentional?
Hi @dmitriz
Please accept my apologies for the delay in responding! This was not on purpose. I did not get any notification from github and so was not aware that you had opened this issue. I have gotten notifications in the past, so was counting on them instead of manually monitoring the issues tab.
You are correct, views are not aware that they are given streams as callback functions. This is on purpose, so that views are not coupled to implementation details.
As you said, flyd
happens to have an api where a stream can be called as a function. But, if you replaced flyd
with another stream library, you would only have to make one change at the top-level code:
const updateStream = someOtherStreamLibrary.createStream();
// let's say you need to call updateStream.emit(val) to emit a value.
const update = val => updateStream.emit(val);
// now you can pass update to views as before.
The rest of the code works without any changes. That is the benefit of not tying view code to stream implementation details.
I'm not sure about the term lifting -- I've heard of it, but am not an expert on its meaning -- but nothing special happens when passing update
when we pass it to views. It is a function. As I showed above, you could wrap the stream library emit
call to be able to pass update
such that views can call update(val)
. If that is called lifting, then yes :) But with flyd
it is already a function so it is passed as-is.
Now, about what is passed to update
: yes, you can pass a value. But in Meiosis, the value itself is actually a function. update
is a stream of functions, as explained in the first bullet in https://github.com/foxdonut/meiosis/wiki/The-Fundamental-Setup#the-meiosis-setup
update is a stream of model updates: that is, a stream of functions that get the current model and return the updated model.
Passing a function that updates the model, instead of passing a value, is very powerful:
- Obtaining a stream of models from the stream of updates is easily done with
scan
. If the workings ofscan
are not clear, please review the previous segment: https://github.com/foxdonut/meiosis/wiki/Quick-Intro-to-Streams - Passing a function that updates the model, instead of a value, means that you can use any model-updating strategy that you like. You can use plain model mutation. You can use a library such as Lodash or Ramda. You can use lenses. You can use Immutable.js. The Meiosis pattern of using functions gives you this flexibility.
Finally, as you said, the anonymous function in the example is mutating the model. Indeed, it is not pure. This is intentional to demonstrate that you can use plain object mutation if you wish. Immutability is not a requirement. But it is certainly possible to use immutable models (Object.assign, Ramda, partial.lenses, Immutable.js, and so on). The choice is yours.
I hope that is helpful!
Thank you for looking at Meiosis! Let me know if I can be of further help.
Hi @foxdonut ,
Many thanks for your answers!
I love your work here and it was one of my inspirations for
the un
project
It seems to have many goals similar to Meiosis.
Would be great to know your opinion about it.
Sorry for being short here, I'll be happy to answer in more details
once I am sure my answers will reach you :)
Hi @foxdonut
Just wanted to mention, we have some related discussion
on some more general composition strategies
with models decoupled from the views:
https://github.com/uzujs/uzu/issues/6#issuecomment-304519136
Would be curious to hear your opinion on it.