How can I efficiently branch on the value inside a

2019-05-10 08:36发布

Let's say I have some application state, maintained on some backend system. It looks like this

data MyState = State1 MyState1 | State2 MyState2

data MyState1 = MyState1 { ms1_text :: Text, ms1_int :: Int }
data MyState2 = MyState2 { ms2_bool :: Bool, ms2_maybe_char :: Maybe Char }

I also have a function to get the latest state from the backend system

getLatestState :: IO MyState

I'm pretty sure I can figure out how to package that up into a Dynamic by repeatedly querying the backend, so that I have

dynMyState :: MonadWidget t m => Dynamic t MyState

I want to render this to html. I want each part of the datastructure to render to a div. However, things that don't exist shouldn't be rendered at all - so, when ms2_maybe_char is Nothing, there should be no div for it, and when MyState is a State1, there should be no div for the State2.

a couple examples for clarity:

State1 (MyState1 "foo" 3)

becomes

<div class=MyState1>
    <div>foo</div>
    <div>3</div>
</div>

and

State2 (MyState2 False Nothing)

becomes

<div class=MyState2>
    <div>False</div>
</div>

Ideally, each part of the DOM should only be modified if necessary - so if ms2_maybe_char changes from Nothing to Just 'a', then a new div needs to be created. Or if ms1_text changes from "foo" to "bar", then we need to change that string in the DOM. However, changing ms1_text should never cause the sibling or parent nodes to be redrawn.

How should I structure my code? Is this even possible, given the getLatestState api as a building block? Am I entirely missing the point of Reflex by trying to build off of a single Dynamic value, and I need to rethink my approach?

In particular, the very first stumbling block is that I can't easily inspect the Dynamic to know if it contains a State1 or a State2. I could potentially use dyn or widgetHold here, and fmap a function over dynMyState which can treat the state as a simple value and generate a m () action that draws the whole thing. But, then I lose all sharing - the entire UI will be redrawn from scratch on every single state change.

Note: this is a more detailed followup question to How can I branch on the value inside a Reflex Dynamic?. What's different/clearer about this question is the additional desire to not lose efficient updates of everything inside the inspected value. Thanks to everyone who helped on that question as well!

1条回答
叼着烟拽天下
2楼-- · 2019-05-10 08:58

The answer depends on exactly what your goals and requirements are. If you want the best possible dom sharing, derived from two separate functions renderState1 and renderState2, I think that calls for virtual-dom.

But it actually sounds like you want to have some precise control over what gets added to the DOM when.

Something simple you can do, if you have modified versions of renderState1 and renderState2 in hand that each take a Maybe State1 or Maybe State2 argument is to build a pair of these dynamic maybes and use css attributes to hide one or the other:

let mState1 = (\c -> case c of
                  s@(State1 _ _) -> Just s
                  _              -> Nothing
              ) <$> dynMyState
    mState2 = (\c -> case c of
                  s@(State2 _ _ _) -> Just s
                  _                -> Nothing
    nothingHider a m =
       let atr = bool mempty ("style" =: "displayNone") . isJust <$> a
       in  elDynAttr "div" atr (m a)

nothingHider mState1 renderMaybeState1
nothingHider mState2 renderMaybeState2

If you derive Prisms then a lot of the awkwardness can be gotten rid of.

查看更多
登录 后发表回答