Open dmitriz opened 7 years ago
@dmitriz
(It is perhaps by now clear that) I find this library really interesting and inspiring.
We are super happy to have you around.
I have never seen the use of generators in this fashion, if just one new idea to be mentioned, among others.
We can't take full credit for that idea. Component
is a monad and monads are hard to use without do-notation. Of course monads and the idea of do-notation is from Haskell. And using generators to imitate do-notation was and idea I first saw in fantasydo.
Specifically, what I mean by "uncomponent" here is to enable this example
...
to be written as pure generator with no external dependency:
Well, that makes a lot of sense. Using go
is already optional in many cases. For instance, one can write div(function*() { ... })
. We already have a mechanism in place for handling all the various things that can be converted into a component. dc1f2a25 adds this to runComponent
so that the top-most component can also be anything that can be converted to a component. I've also changed the simple example to make use of this.
If I understand your goals in un correctly the intention is that one can write components that have no dependencies on any framework? I think that sounds cool. But doesn't it add some severe restrictions to the type of architecture one can implement?
@paldepind
We are super happy to have you around.
Thank you, that means a lot to me!
I have never seen the use of generators in this fashion, if just one new idea to be mentioned, among others. We can't take full credit for that idea. Component is a monad and monads are hard to use without do-notation. Of course monads and the idea of do-notation is from Haskell. And using generators to imitate do-notation was and idea I first saw in fantasydo.
I think I had in mind the idea to use it for multiple outputs coming out from a UI component. That have a "parallel" flavour comparing to the Haskel's sequential do
, which is of course not new to you, but I didn't find the connection obvious.
But now that you mention the do
/go
, I had a second thought that maybe there should be several roles for the generators, otherwise some people may feel there is "too much magic".
In which case, simply reducing all role to the same looking generator function, may lead to a loss of its role. What I mean is that sometimes you may actually want the proper generator functionality, sometimes you want to use it with the go
, and sometimes with the loop
. In the first case, you have to run it manually like normally in JS, in the second it used for the sequential go
and in the last case it is "looped", where the output becomes available as part of the input. You did not include go
as prominently as loop
on the main page, so I must have been confused by that. But perhaps it should be part of the core API needed to provide the generators with different roles they can play in the framework?
And if the answer is yes, it would have its place along with other core API:
const view = ({ div, span, input, go }) => go(function* () { .... })
which could be again exported with no dependency.
But now that you mentioned Monadic structure, I could think of perhaps another, simpler way to write this particular example even without generators:
const main = () =>
span("Please enter an email address: ")
.chain(() => input())
.map(({inputValue}) => map(isValidEmail)(inputValue))
.chain(valid => div([
"The address is ",
valid ? 'valid' : 'invalid'
]))
Would it work?
If I understand your goals in un correctly the intention is that one can write components that have no dependencies on any framework? I think that sounds cool. But doesn't it add some severe restrictions to the type of architecture one can implement?
Perhaps I will run into more difficulties later on, but at this stage, when I am still shaping the ideas, it does not feel so much of a problem. Not yet. :)
There is the same pattern I can see across various frameworks, where you always have views mapping state into dom and models responsible for updating the state. So it feels natural to leverage this and encapsulate in the same architecture pattern, at least at this level of abstraction.
But otherwise, anywhere else, I like the idea of giving the complete freedom how to write the views and models. So with Turbine, you would even use the generators sometimes wrapped into their specific roles. And internally, there wouldn't be any restrictions at all. You would write exactly the same code as you would write anyway. And some people may prefer to carry their dependencies along rather than export them as parameters, which is fine. Of course, that would add some rigidity and limit the "internal universality" of your components. By which I mean e.g. using the Snabbdom
flavour to write dom in React
or Mithril
and vice versa.
But you can still keep the "external universality" for your Turbine components, that is the ability to plug them into outside frameworks. And only at the very top root level, your component would need to conform to some pattern. For instance, to be used inside a larger React app, you would wrap your Turbine component as React's one and then use it the normal way without even noticing the difference. And you can write Turbine's components with advanced functionality, that people can use in React without even paying attention :)
The only API you would need to connect with React is a Renderer,
whose function is to attach the dom (or vdom) element to the parent,
and then to be able to subscribe to the updates.
Internally it would recognise whether you attach to the real dom element or
to some vnode inside a framework.
In case of React, that means either calling React.render
or
wrapping into a thin React.Component
class that would live inside its parent and update
via the React's native forceUpdate
method.
So there is some adaptor to write here,
but is it a universal one to work for any library
wanting to get embedded into React.
Which on its root side, needs to provide a dom-like node,
which it does anyway, along with its state stream,
to be sent directly into the React's renderer.
And even if it is not a reactive library like Redux,
the un
can convert their stores into state streams,
and now you are back to the same api.
I'd be very curious to hear your opinion, if that makes sense, and any problem you might see there coming along the way.
BTW, here is how I would see a possible way to embed un
-component inside the Turbine:
https://github.com/uzujs/uzu/issues/5#issuecomment-301454959
So all that can be done in simple-minded redux-style way, can be encapsulated away into plain uncomponents unaware of the streams.
These could even be React or other framework functional components.
Then once mounted as Turbine's component, full control is passed to the Turbine. But now Turbine can take care of higher level stuff without caring about the plain details :)
@dmitriz
Would it work?
Yes, indeed. That example should work. In fact, one can always rewrite Turbine code to eliminate the use of generator functions. They are not essential. But they make things a lot easier 😄
But now that you mention the
do
/go
, I had a second thought that maybe there should be several roles for the generators, otherwise some people may feel there is "too much magic".In which case, simply reducing all role to the same looking generator function, may lead to a loss of its role. What I mean is that sometimes you may actually want the proper generator functionality, sometimes you want to use it with the
go
, and sometimes with theloop
. In the first case, you have to run it manually like normally in JS, in the second it used for the sequentialgo
and in the last case it is "looped", where the output becomes available as part of the input. You did not includego
as prominently asloop
on the main page, so I must have been confused by that. But perhaps it should be part of the core API needed to provide the generators with different roles they can play in the framework?
I am not sure what you means that the should be several roles for the generator functions? In all cases, the generator functions do the same thing. They're sugar for calling chain
. I've made a PR #38 with some additions to the documentation. It contains a section about our use of generator functions. I'm not sure if it's understandable but I try to make the point that generator functions are just an easier way to call chain many times.
Yes, indeed. That example should work. In fact, one can always rewrite Turbine code to eliminate the use of generator functions. They are not essential. But they make things a lot easier 😄
Ah, that is good to know ;) Yes, big nesting can be annoying, but the generators carry their complexity cost, so without deep nesting the plain function way feels easier.
Even for that example, I'd find it "tolerably" awkward if I writing as
input()
.chain( ({ inputValue: a }) => input()
.chain( ({ inputValue: b }) => span(["Combined text: ", a, b]) ).
)
Of course, you only need nesting when the current function needs results from computations before the last one, otherwise you can nicely chain them without nesting, but I see your point.
But what is less clear is what is a
in this example:
div(`Hello`)
.chain( a => div( ... ) )
Something like vnode
inside the Mithril view function?
In other words, is the element itself also part of the output?
Yes, any further explanation for topics such as generators helps, as those are quite new for many people, and I don't quite feel their usefulness is properly explained in most articles.
It might also be good to give the link to Jabz for more details on the go
function,
since it is a non-standard feature, at least in JS.
You probably didn't mean to write do
in your examples.
About the roles of generators, I was thinking of the use case
when you show the user a sequence of input fields, one at a time.
So you stop, and resume when new entry arrives.
Would that be another role for generators, where the go
would not run?
It would be interesting to model this situation as simple generator run,
where you can use user's submit event for the next generator run,
which would display the next form and so on.
Here you use it for resuming rather than do-notation,
which is what I've had in mind by another role.
Does it make sense?
@dmitriz
But what is less clear is what is
a
in this example:div(`Hello`) .chain( a => div( ... ) )
Something like
vnode
inside the Mithril view function? In other words, is the element itself also part of the output?
The element is part of the Component
but not part of the output. If you do div("Hello").chain(a => ...)
then a
will be an object of all the events on a div
element. It will have click
, mouseover
, etc.
About the roles of generators, I was thinking of the use case when you show the user a sequence of input fields, one at a time. So you stop, and resume when new entry arrives. Would that be another role for generators, where the go would not run? It would be interesting to model this situation as simple generator run, where you can use user's submit event for the next generator run, which would display the next form and so on. Here you use it for resuming rather than do-notation, which is what I've had in mind by another role.
I think that is an interesting idea and it would be exciting to explore. But for now, in Turbine, I think we should try to focus on using generators as normal do-notation. Generators are already confusing enough and I like that we can explain that they only do one single and pretty simple thing (i.e. call chain
in a nicer way).
(It is perhaps by now clear that) I find this library really interesting and inspiring.
I have never seen the use of generators in this fashion, if just one new idea to be mentioned, among others.
I really would like to leverage the
un
project to make it even more accessible and pluggable.So anyone can try the
Turbine
with a simple piece of code, and then instantly plug and use inside any existing working application.Uncomponentisation
Specifically, what I mean by "uncomponent" here is to enable this example
to be written as pure generator with no external dependency:
It is almost the same but now can be used and tested anywhere in its purity. It would be recognised and packaged with
go
internally by the Turbine at the runtime.Universality
What I mean by universality is to be able to plug this into any application. Here is how the
un
tries to make it work:Use the
mount
function to run a Turbine's component directly in DOM:or embed it anywhere into any framework:
Here is an example of the working code
Here is what I think is needed to make it possible:
createElement
function to compile the user uncomponents into Turbine componentscreateRender
function to run the componentIt would be great to hear what you think about it.