question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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?

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:13 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
seagreencommented, Feb 10, 2020

A few more thoughts:

Making sure I understand the problem

One of the most enjoyable things about Concur to me is having local state at the leaves. Imagine in a strategy game, you’ve got things like open help tooltips, partially filled out forms (for things like setting what a base is producing), all that kind of stuff.

When doing this style of programming nothing above the leaf level can ever recurse on itself. If it does it will wipe out the local state of all its children.

So I think getting sharing of values like this right is going to be very important.

The current solutions

Imagine you’re making a level editor for a game. You want a form that can be displayed permanently on the screen for making new unit types. It has a “Submit” button to make a new unit type. When that’s hit you want to communicate it to the rest of the UI, but you also want to leave the state of the form alone, on the guess that the settings like speed, firepower, etc might be similar for the next unit they create, and they will only want to tweak them instead of starting from scratch.

Currently with concur-core the only way to get values out of a widget is to return them, so you’re basically forced into TEA: InternalState -> Widget html (Either InternalState NewUnitType). If you don’t report the InternalState to your parent, it won’t have it on hand to re-initialize you once you return a NewUnitType.

The Δ strategy is definitely an improvement on this. You can make its type Δ NewUnitType -> Widget html a.

However, this isn’t as descriptive as we could be, because gives the widget the power to use the Δ for both reading and writing, but we only want to use it for writing.

An idea

What about parameterizing Widget itself? We could have a WidgetStream html read write return and then do type Widget html a = WidgetStream html Void Void a.

Then, for this example, the type of the unit designer would be WidgetStream html Void NewUnitType a.

This might be a horrible idea, but I thought I’d throw it out there in case it’s interesting.

1reaction
seagreencommented, Jan 26, 2020

Quick experience report: I’ve been doing Concur (well concur-replica) programming for about a month now, and the scenario described in this issue was becoming a very serious problem.

If I hadn’t seen this issue I wouldn’t have known what to do-- happily I was able to copy the local and with implementations. They’ve worked well so far.

For the sake of new users and Concur adoption, should we consider moving them or some alternative solution into the library?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Best Resume Widget for your website! - Elfsight
Resume widget is a handy tool allowing you to easily expose your work experience right on your website in an attractive card with...
Read more >
Add widgets on iPhone - Apple Support
Widgets show you current information from your favorite apps at a glance—today's headlines, weather, calendar events, battery levels, and more.
Read more >
Trending Widgets tagged as resume template - Figma
Widget results for #resume template. All · Files + templates · Plugins · Widgets. Figma + FigJam. Editor. Figma + FigJam. Figma. FigJam....
Read more >
Flutter: Update Widgets On Resume? - dart - Stack Overflow
You can listen to lifecycle events by doing this for example : import 'package:flutter/material.dart'; ...
Read more >
Resuming a recording session - IBM
You can resume a recording session in an AccessProfile with the bundled Widget_PSR_Resume widget. Before you begin. You must be familiar with adding...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found