Closed rvion closed 7 years ago
basically, if I want to spawn 3 components
app :: forall e. VDom e
app = div
[ root 1.0
, root 12.0
, root 100.0
]
the 2 versions below do not behave identically, according to where the sliderEvents is declared, and according to the amout of optimisation purescript is doing (🔴 using something like rollup-plugin-purs will change the behaviour.)
version 1: all sliders share the same variable
root :: forall e. Number -> VDom e
root a = ul
[ input
[ tpe := "range"
, inputNumber ==> sliderEvents
, max := a
]
, div [children <== imageLists]
]
where
imageLists = sliderEvents.src # map (\n -> replicate (round n) (img[src := "img.svg"]))
sliderEvents :: forall e. Handler e Number -- <========== outside of the where clause
sliderEvents = createNumberHandler []
version 2: all sliders are independent, but only because there is not much optimisation.
root :: forall e. Number -> VDom e
root a = ul
[ input
[ tpe := "range"
, inputNumber ==> sliderEvents
, max := a
]
, div [children <== imageLists]
]
where
imageLists = sliderEvents.src # map (\n -> replicate (round n) (img[src := "img.svg"]))
sliderEvents :: forall e. Handler e Number -- <========== outside of the where clause
sliderEvents = createNumberHandler []
📝 keeping unsafeCreateXxxHandler
functions available would be a good idea, though.
once inlining will be mainstream, NoInlinePragma will probably be too. Since thread safety is not always important, being able to quickly define global top level handlers may come handy in lots of situations
From what I understand, creating handlers during instantiation is needed for various design paterns, such as for components to be able to manage an inner state.
So, the problem with making createHandler effectful is that it forces the whole widget creation to be effectful.
instead of
root :: forall e. Number -> VDom (console :: CONSOLE |e)
root a = ul
[ input
[ tpe := "range"
, inputNumber ==> sliderEvents
, valueShow := 0
, max := a
]
, div [children <== imageLists]
]
where
imageLists = sliderEvents.src # map (\n -> replicate (round n) (img[src := "img.svg"]))
sliderEvents = createNumberHandler []
it would mean writing component definition in some VDomBuilder monad like that:
pseudocode example 1:
root :: forall e. Number -> VDomBuilder e
root max = do
sliderEvents <- createHandler
let imageLists = sliderEvents.src # map (\n -> replicate (round n) (img[src := "img.svg"]))
pure $ ul
[ input
[ tpe := "range"
, inputNumber ==> sliderEvents
, valueShow := 0
, max := a
]
, div [children <== imageLists]
]
pseudocode example 2 (taking some inspiration from haskell libraries like Blaze, Lucid, bytestring-builder, etc., and pushing the builder pattern further):
root :: forall e. Number -> VDomBuilder e
root max = do
sliderEvents <- createHandler
let imageLists = sliderEvents.src # map (\n -> replicate (round n) (img[src := "img.svg"]))
ul_ do
input_ do
tpe_ "range"
inputNumber_ sliderEvents
valueShow_ 0
max_ max
div_ $ children_ imageLists
📝 I think it really fits the builder pattern.
📝 also, when writing a presenter component that only use its parameters for input and handlers, everything remains compatible, the only thing needed is a function to convert a pure VDom e
to a VDomBuilder e
(probably pure
since VDomBuilder
would be a monad)
Eventually, the only thing remaining woud be a new render function
render :: forall e. String -> VDomBuilder e -> Eff (vdom :: VDOM | e) Unit
@LukaJCB @sectore : does it make sense to you ?
Hi @rvion, it's indeed something that I've been trying to improve in the last few months. I've been thinking and experimenting a lot and I think longer term, I'd like to move away from Handlers all together in favor of a "pull-only" Api, where you never get access to a Handler, but instead only get an Observable.
I'm still in the process of weighing the pros and the cons of such a solution, but I think it'd be preferable to what we have right now.
Your solution is also really interesting, I'll have to see how it stacks against other solutions, I really really appreciate your time and your effort that went into this.
I initially designed this API for Scala, where referential transparency is not as big an issue. It's also heavily influenced by pre-0.17 Elm, where you also have the problem that the mailbox
function is not referentially transparent. They removed that API though, and I'd also like to get rid of it in the long term.
@LukaJCB great to hear the project is alive !! :) I spent most of the night thinking about this problem.
here is what I came up with https://github.com/OutWatch/purescript-outwatch/issues/8
I tried various different designs: this one is best one I found so far based on all constrainsts I gave myself:
build
converts a VDomBuilder e
to a VDom e
only once at startup, and the render function is not changedI didn't test it extensively, so there might be important problems I did not see
📝 as a bonus, the monadic api could allow user to suply their own monadtransformers with some reader monad providing configuration, or globally available message bus via handlers. Halogen permit this, and this is from what I saw very usefull (I played with elm pre 0.17 and post 0.17, pux, halogen and various other UI libs, and the pure functional paradigm quickly becomes annoying without this. Here, it comes for free, at almost no cost)
Here is a small visual example (nothing new)
module Example.Monadic.Counter where
import Control.Monad.Eff.Random (RANDOM, randomInt)
import Control.Monad.Except.Trans (lift)
import OutWatch.Monadic.Attributes (childShow_, click_, text_)
import OutWatch.Monadic.Tags (button_, div_, h3_)
import OutWatch.Monadic.Types (HTML)
import OutWatch.Monadic.Utils (createHandler_, cmapSink)
import OutWatch.Pure.Sink (createHandler)
import Prelude (bind, Unit, (+), (#), const, negate, ($), show)
import RxJS.Observable (merge, scan, startWith)
incrementHandlder = createHandler [0]
decrementHandlder = createHandler [0]
count = merge incrementHandlder.src decrementHandlder.src
# scan (+) 0
# startWith 0
app :: forall e. HTML (random :: RANDOM|e) Unit
app = do
rnd <- lift $ randomInt 10 20
div_ do
div_ do
text_ "some random number:"
text_ (show rnd)
button_ do
text_ "Decrement"
click_ (decrementHandlder # cmapSink (const (-2)))
button_ do
text_ "Increment"
click_ (incrementHandlder # cmapSink (const ( 2)))
h3_ do
text_ "Monadic Counter: "
childShow_ count
when I render the widget twice:
app :: forall e. HTML (random :: RANDOM|e) Unit
app = do
rnd <- lift $ randomInt 10 20
let incrementHandlder = createHandler [0]
let decrementHandlder = createHandler [0]
let count = merge incrementHandlder.src decrementHandlder.src
# scan (+) 0
# startWith 0
div_ do
div_ do
text_ "some random number:"
text_ (show rnd)
button_ do
text_ "Decrement"
click_ (decrementHandlder # cmapSink (const (-2)))
button_ do
text_ "Increment"
click_ (incrementHandlder # cmapSink (const ( 2)))
h3_ do
text_ "Monadic Counter: "
childShow_ count
when I render the widget twice:
From what I understand, as of now, it's only working because of purescript lack of optimisation. both inlining and caching should break the code.
Am I missing something ? edit: Is there something related to strictness I'm not understanding here ?
should it be something along the lines of: