Closed jterbraak closed 10 months ago
It's an interesting idea and has come up before.
I'm still on the fence about it. Check out, for example, this draft of the new deku docs: https://righteous-rain.surge.sh/. Go to the Effect
section and scroll all the way down to the end-ish section for game developers, where there's a little game. You can click Start Game. The goal is to try to click the circles before they disappear. It's maddening.
This uses a more elaborate effect system than a provider: one that essentially creates a game engine. That said, the whole example is only 300 loc, so making these things doesn't take that long once you get the hang of it.
When I've used deku to make games and instruments, I always wind up doing some variation on this & create a custom effect system. The documentation also uses this strategy. Check out https://github.com/mikesol/deku-documentation/blob/7c91f3cb4269af8891edf500c91a75d0e459fac1/src/Contracts.purs#L102, for example. That's the way pages get subbed in and out and cancel any ongoing effects in case there are any (like, for example, if a page has started a timer in one of the examples). This is a free monad, and you can see that one of the branches in the ADT is GetEnv
, which is a provider.
My concern with building providers into the framework is that it commits to one particular effect system - the reader monad - instead of letting people build their own. We could theoretically provide starter effects systems for folks, but I've found that when I start a new project, I start with the effect system and let that guide how the game/instrument develops. So I don't know how much mileage prefabbed effect systems would give people. But I'm not opposed to it if it's useful! Just stating my bias based on my own usage of the framework.
So the discussion is between between having Nut
be the base abstraction of your application or something custom made. Initially I considered every component of an application to be some sort of Nut
or a -> Nut
and your argument is that it's probably a better idea to build your own types on top of Nut
.
I wanted to build a more consistent subset of HTML anyway ala elm-ui
so it's an approach worth trying out.
One typing I did try Array ( f ( Poll ( Attribute r ) ) ) -> Array ( f Nut ) -> f Nut
which has some interesting properties and works surprisingly not awful:
data ContextF a
= GetEnv ( Env -> a )
| GetRandom ( Number -> a )
| MakeStyle String ( Attribute' -> a )
...
elementify3 :: forall f r . Monad f => String -> Array ( f ( Poll ( Attribute r ) ) ) -> Array ( f Nut ) -> f Nut
elementify3 tag fattr fchildren = do
attr <- sequence fattr
children <- sequence fchildren
pure $ elementify2 Nothing tag attr children
...
main :: Effect Unit
main = do
setPresence /\ presence <- liftST $ DE.useState false
app /\ style <- runWriterT $ foldFree ( interp { title : "some title" } ) do
li []
[ div [ makeStyle "background-color:red;" ] [ text_ <<< _.title <$> getEnv ]
, map ( guard presence ) $ div [ makeStyle "color:red;", makeStyle "background-color:blue;" ]
[ text_ <<< show <$> getRandom ]
, button [ pure $ DL.click $ presence <#> \p _ -> setPresence $ not p ] [ pure $ text_ "hide" ]
]
runInBody $ fixed [ app, D.style_ [ text_ style ] ]
This naively collects all styles and renders them at the bottom in a stylesheet. Although I haven't figured out how to make switcher
work yet.
Do I understand correctly that your goal is to create a style sheet for all styles that are present on an initial render? If so, a couple questions:
So the documentation recognizes the need for implicit propagation of application state. And while it does give some solutions I don't think it's all that implicit, requiring a left bind per "component". It also forces mixing
do
levels, which the documentation also notices as a problem. To solve this I would like to change the signature ofNut
something likeenv -> ANut
andAttribute
toenv -> { key :: String, value :: AttributeValue }
. All elements and attributes provided by the DOM modules and control functions likeswitcher
andguard
receive this new signature and compose. The internalANut
would stay as is. The DOM-tree values would propagate theenv
to their children andAttribute
s. TherunIn...
functions then get a parameter to inject the initialenv
. Although you could always just apply theenv
yourself and change the signature to only run aANut
. Adding aContravariant
instance would add the ability to hoistNut
s between environments viacmap
if they don't fit in the suggestedNewtype Record
model. The provided hooks would have to change as well of course. Maybe add auseEnv
oruseContext
to get explicit access to theenv
.