Imagine the following scenario:
counter x = do
_ <- div [ onClick ]
[ text $ T.pack (show x)
]
counter (x + 1)
other :: T.Text -> Widget HTML T.Text
other str = do
e <- div []
[ input [ onInput, value str ]
, text str
]
pure $ targetValue $ target e
container str = do
newStr <- div [] [ counter 0, other str ]
container newStr
I.e. a composition of both neverending and non-recursive widgets. The problem is that every time other finishes, counter is going to lose its state.
To fix this, we could "ban" recursion (and thus neverending widgets) and explicitly thread arguments between parent and children components, essentially emulating Elm, but in a somewhat free-form way. However, disallowing recursion isn't even the worst thing; to fix state loss, instead of writing a widget like this:
workflow = do
a <- step1
b <- step2 a
...
pure b
one would have to turn the above into a state machine:
workflow = do
st <- get
case st of
Step1 -> do
a <- step1
put (Step2 a)
Step2 a -> do
b <- step2 a
put (Result b)
To me, reifying time flow is the selling proposition of Concur and something no other UI paradigm offers, to my knowledge. Going back to explicit state machines in the spirit of React or Elm doesn't make much sense.
I've thought a bit about this but the solution I've come up with feels a bit off. Basically, we'd change the type of orr to:
orr :: [Widget v a] -> Widget v (a, [Maybe (Widget v a)]) -- specialised to Widget
I.e. orr returns both the value of the ending Widget, as well as all the continuations of the remaining Widgets at that point. With this, we could rewrite the first example to:
resume = flip fromMaybe
container str c = do
(newStr, [c, _]) <- div [] [ resume c $ counter 0, other str ]
container newStr c
But this does not seem ideal. It would be nice if we didn't have to modify orr for this, but then there would be no way to get hold of the continuations of the non-firing Widgets. I think it should be possible to write something like this:
reify :: Widget v a -> Widget v (a, [Maybe Widget v a])
which would return the result along with all the continuations of a Widget's children, but being able to break the encapsulation of the otherwise fully opaque Widget type that easily is probably a bad idea.
I've also thought about crazy stuff like actually calling all continuations after a Widget ends, effectively running the world in parallel and introducing a join combinator - which somehow collects the results from the different "parallel universes" - but that seems like it would be awfully inefficient and probably not even possible. Sounds cool though.
Maybe I'm overlooking something fairly obvious. I saw the Gen stuff in the Purescript repo and thought about making each Widget a pipe-like thing along with yield and await operators, so that outside state can be "pushed" into neverending widgets, but this wouldn't help if widgets can still finish and thus force their siblings to lose state.
I've also had the idea of ditching the Monad constraint altogether and making Widget a selective Applicative, which still allows for some control flow but is fully introspectable. This would bring the benefit of being able to collect every UI transition upfront (and maybe even precompute DOM diffs) but more importantly, of allowing us to attach the continuations directly to the Widget VDOM node (which would never change).
However, although SelectiveDo might be implemented someday, until it isn't it's fairly cumbersome to program with selective Applicatives. So that's off the table, at least for now.
Do you have any thoughts on this?
Imagine the following scenario:
I.e. a composition of both neverending and non-recursive widgets. The problem is that every time
otherfinishes,counteris going to lose its state.To fix this, we could "ban" recursion (and thus neverending widgets) and explicitly thread arguments between parent and children components, essentially emulating Elm, but in a somewhat free-form way. However, disallowing recursion isn't even the worst thing; to fix state loss, instead of writing a widget like this:
one would have to turn the above into a state machine:
To me, reifying time flow is the selling proposition of Concur and something no other UI paradigm offers, to my knowledge. Going back to explicit state machines in the spirit of React or Elm doesn't make much sense.
I've thought a bit about this but the solution I've come up with feels a bit off. Basically, we'd change the type of
orrto:I.e.
orrreturns both the value of the endingWidget, as well as all the continuations of the remainingWidgets at that point. With this, we could rewrite the first example to:But this does not seem ideal. It would be nice if we didn't have to modify
orrfor this, but then there would be no way to get hold of the continuations of the non-firingWidgets. I think it should be possible to write something like this:which would return the result along with all the continuations of a
Widget's children, but being able to break the encapsulation of the otherwise fully opaqueWidgettype that easily is probably a bad idea.I've also thought about crazy stuff like actually calling all continuations after a
Widgetends, effectively running the world in parallel and introducing ajoincombinator - which somehow collects the results from the different "parallel universes" - but that seems like it would be awfully inefficient and probably not even possible. Sounds cool though.Maybe I'm overlooking something fairly obvious. I saw the
Genstuff in the Purescript repo and thought about making eachWidgetapipe-like thing along withyieldandawaitoperators, so that outside state can be "pushed" into neverending widgets, but this wouldn't help if widgets can still finish and thus force their siblings to lose state.I've also had the idea of ditching the
Monadconstraint altogether and makingWidgeta selectiveApplicative, which still allows for some control flow but is fully introspectable. This would bring the benefit of being able to collect every UI transition upfront (and maybe even precompute DOM diffs) but more importantly, of allowing us to attach the continuations directly to theWidgetVDOM node (which would never change).However, although
SelectiveDomight be implemented someday, until it isn't it's fairly cumbersome to program with selectiveApplicatives. So that's off the table, at least for now.Do you have any thoughts on this?