purescript-concur / purescript-concur-react

Concur UI Framework for Purescript
https://purescript-concur.github.io/purescript-concur-react
MIT License
271 stars 17 forks source link

Implement MonadRec #1

Closed anttih closed 6 years ago

anttih commented 6 years ago

Do you have ideas on how to implement this? It seems to me that you cannot do this with the current formulation of the Widget datatype. I also tried using Free but wasn't able to yet write the Alt instance for that.

Free uses a sequential datastructure for the binds, maybe this approach could work here as well. I'm not sure.

ajnsit commented 6 years ago

Well the reason why I didn't spend much time on writing a MonadRec instance is that I'm not sure it is needed. Since this implementation is based on React, and uses an effectful setState to traverse the binds, it basically implements a trampoline for recursive computations. I am about 95% sure that this means that rendering a widget will never have problems.

Please let me know if you think that I'm not thinking this through properly, and have missed some case.

anttih commented 6 years ago

But even the basic counter example (pun not intended):

counter count = do
  newCount <- button $> count + 1
  counter newCount

will blow the stack, right? You'd want to call tailRecM there to do the trampolining. It would also be helpful to have forever available to loop at the top level.

Ps. I actually have POC implementation of halogen-vdom so it would be nice to not rely on react's implementation details.

ajnsit commented 6 years ago

Actually no. That example should not blow the stack for the reasons I mentioned. Do notation basically desugars to binds, and the way bind for widgets is currently implemented, recursive calls do not increase the stack.

Also, while this implementation is react specific, I imagine it's possible to abstract it in a way to make this safe in other backends as well. I'll start thinking about how to do that.

The halogen-vdom implementation sounds cool! Do you have a repo somewhere?

anttih commented 6 years ago

Interesting, indeed that does seem to be stack-safe.

I see your point about the react implementation being stack-safe. But how would you run a StateT widget forever? StateT itself is MonadRec but it relies on the underlying m to be one as well. I think you could do it but you would need to unwrap and loop the StateT in "userland" code. Basically if we had the MonadRec instance a lot of the monad transformers usage would be much easier. You could just stick the forever call at the top.

The vdom version is not anywhere yet.

ajnsit commented 6 years ago

I see your point. As a short term solution, I will add a trivial MonadRec instance. How does this look? -

instance renderMonadRec :: MonadRec (Widget v eff) where
  tailRecM f a = go =<< f a
    where
      go (Loop a') = tailRecM f a'
      go (Done b) = pure b
anttih commented 6 years ago

Nice! It was that simple after all. So this should be stack-safe as long as the backend is stack-safe?

ajnsit commented 6 years ago

Yup! It's a trivial instance that offloads all the work to the backend.

anttih commented 6 years ago

Ship it!

ajnsit commented 6 years ago

Done! Added a tail recursion example as well. Source, and Demo.