Open meditans opened 8 years ago
Hi @meditans, thank you! That's correct - it should be possible to introduce some syntax to return arbitrary values from within a JSX block. Something like:
{myVal} <- [jsx| <div>myVal@{ return "hello"}</div>|]
Basically, you'd need to introduce a new record type declaration from within TH, and then client code can deconstruct it (with something like NamedFieldPuns
shown above). I was messaged with this idea just a week ago, and they may be working on an implementation at some point. If you have other ideas, feel free to let me know or make a PR!
Trying to participate in a discussion I know absolutely nothing about, but just wondering how React's version of JSX solves this? Or do they somehow not need to solve this problem?
In React's JSX is simplier, because there's no monadic flow:
giveMeClickEventSomehow <- [jsx|<foo>...</foo>]
instead there is:
<foo onClick={this.someHandler()}>...</foo>
Just thinking aloud here, would this be easier to solve with Angular 2's approach? The HTML "extensions" to help them in tying click-handlers and bindings (dynamic values) to Javascript.
@saurabhnanda this approach is quite tempting, and it's being explored for example by http://try.websharper.com/example/todo-list (just check how they use F# type providers to have statically checked HTML!). I'm wondering, however, whether it's a good fit at all for library like reflex-dom, as it's much more expressive than the usual: "state -> DOM" rendering...
Yes, Reflex and React are quite different in their approach to application development. I think of React as more of an "immediate mode rendering engine", which is to say it takes your properties and renders them whenever they change. You're expected to use the imperative nature of JS to handle events and tweak data as necessary - React doesn't have a lot to say about how you should do that (unless you want to use an addon like Redux which handles application state and data flow). Since any data could theoretically change at any time, React has a clever virtual-DOM-diffing mechanism to calculate the smallest DOM update and schedule it for rendering at the next animation frame.
Reflex, on the other hand, is a functional reactive programming library where streams of data/events are connected together to get interactivity. Reflex can know the parts of the application where change is even possible, and exactly what other streams can cause that change. This means the rendering engine can just render the DOM as it updates instead of needing to check the whole application each frame for changes (although I'm not 100% sure what the implementation looks like on the inside). Not only that, but you can end up with well-encapsulated components, where the inputs and outputs are well-defined and easy to test.
One consequence of this, though, is that more complicated apps tend to have lots of data flowing around, and look less like a static chunk of HTML (because event-handling is "local" instead of "out of band" like React). I think this is the reason that reflex-jsx
has a problem that React itself doesn't seem to have, where it's useful to "return" values from within the jsx expression.
(Mostly just agreeing with @BartAdv, but wanted to provide a little more context in case it was useful)
FWIW, I am working on a solution to this - I haven't yet gotten something usable, but will ping this thread with the PR when I do, and I'm happy to get feedback on the design (as it will necessarily introduce new syntax)!
Hi @dackerman, thanks for the detailed info. Btw, if you want to share the work in progress, I may be able to help with some code, or give you early feedback!
much more expressive than the usual: "state -> DOM" rendering...
Reflex, on the other hand, is a functional reactive programming library where streams of data/events are connected together to get interactivity. Reflex can know the parts of the application where change is even possible, and exactly what other streams can cause that change. This means the rendering engine can just render the DOM as it updates instead of needing to check the whole application each frame for changes (although I'm not 100% sure what the implementation looks like on the inside). Not only that, but you can end up with well-encapsulated components, where the inputs and outputs are well-defined and easy to test.
Is this a good place to start a philosophical discussion about how exactly is Reflex (or FRP frameworks) better than React? Over at https://github.com/vacationlabs/haskell-webapps/tree/master/UI/ReflexFRP/mockLoginPage we're playing around with Reflex and I'm unable to get an intuition for this myself.
Is there a document which describes what is the problem in existing UI frameworks that Reflex is trying to solve?
@saurabhnanda I highly recommend Ryan Trinkle's talks on reflex - part 1 and part 2. You can also discuss at https://www.reddit.com/r/reflexfrp/. Ryan is active on that subreddit so if it hasn't been asked before, you could bring up the topic and he's likely to weigh in.
Hi @dackerman I got around developing this feature: if you're interested we could talk a bit about the design of the library and then work towards a PR!
Hi @meditans, @nmattia and I had been working on this to some extent, but I confess I don't have a lot of time so any help is appreciated! This issue is probably as good as any to discuss potential solutions (feel free to chime in, @nmattia, with your ideas or code as well).
Here are some goals I think we should strive for:
Things I'm not too concerned about:
The syntax should be pretty unambiguous to parse, and low overhead to specify. My thoughts were something along the lines of {variableName@expression}
to signify that it should return a record with the field variableName
and value that expression
evalulates to. I'm open to other ideas here.
There are a couple ideas, I'll present both.
One idea is to allow the user to create a custom record for the return type of a jsx block.
Something like:
data UserWidget t = UserWidget
{ usernameInput :: TextInput t
, submitClicks :: Event t ()
}
ui :: MonadWidget t m => m (UserWidget t)
ui = do
[jsx|
<div>
<label>Username: {usernameInput@textInput def}</label>
{submitClicks@button "Submit"}
</div>
|] def
Essentially, the JSX block itself would generate a function with a type signature like:
MonadWidget t m => UserWidget t -> m (UserWidget t)
The user would have to pass in a default value to the expression so it could build up the fields from within the function without needing to know how to create the type explicitly.
I mostly implemented this (as a prototype), but I ran into some issues with the typechecker needing a more explicit type signature on the function itself. Also, it's maybe not easy to make a default value for some types, and definitely requires some boilerplate.
Another idea is to generate a type in TH from within the block, and stick the fields on there. This would mean you'd have to do something like this (assuming NamedFieldPuns
):
ui :: MonadWidget t m => m ()
ui = do
{usernameInput, submitClicks} <- [jsx|
<div>
<label>Username: {usernameInput@textInput def}</label>
{submitClicks@button "Submit"}
</div>
|]
-- do something with the fields
return ()
The downside is you wouldn't know the type, and so might be restricted in how you use the result (i.e. you have to destructure it or not specify a type). Maybe we could get around this with syntax like:
[jsx|UserWidget
<div>
<label>Username: {usernameInput@textInput def}</label>
{submitClicks@button "Submit"}
</div>
|]
That would specify the type of the record to generate, resulting in something lik e data UserWidget = UserWidget {...}
. The major roadblock I see to this approach is that I don't know how to infer the type of the fields from within TH. The field values are arbitrary expressions, so I don't if there is TH magic to figure out what the type of the whole thing is at generation time.
If anyone has ideas about how to do this, I am all ears!
Anyway, I appreciate people's interest - feel free to throw in your ideas or submit PRs for us to discuss.
Here are some thoughts on the design.
At some point I needed a solution quickly and didn't have too much time to spend on it. I simply returned a tuple of the value of the current call and the "rest":
<div></div>
<p>Hello</p>
will be turned into
(,) <$> (el "div" $ return()) <*> ((,) <$> (el "p" $ text "Hello") <*> ())
The branch is here.
I used (,)
to carry the return type. However for nodes with n
children we
could return an n
-tuple, which would make the matter a bit nicer. Also we can
use lens to get values out, once we know where that value actually is. This
highlights the biggest limitation: we can't really name the values. And because
of that we depend on the structure of the returned object, which will change
whenever the quasiquote changes. Also the return type is polluted with a lot of
values that we don't need.
error:
• Couldn't match expected type ‘((),
(((),
(((), ((), ())),
((),
(((),
(((),
(a,
((),
(((), (b, ((), ((), ())))),
((), (c, ((), (d, ())))))))),
())),
())))),
()))’
with actual type ‘(a, b, c, d)’
The error messages aren't actually that bad, the types are. I usually copy paste the type from the error message as the value-level tuple. Ew.
We replace all (named) values of type m a
with a value of type m (Map String a)
. Then we append the maps together and return it to the user. The user can
then lookup values using a String
as the key. Obviously the main drawback
here is that we lose some safety: the user gets a Maybe a
from lookup
even
though we should be able to assure that a key/value pair is present. Also, I'm
not sure how to deal with the fact that all values have different types.
I haven't tried implementing this one, so I won't say too much.
Easy to implement
Maybe
.lookup :: Map k v -> k -> Maybe v
I used to really like that one, however now I see a few flaws with the design. First, we need a constructor to pattern match on. That is you can't just call
{x, y} <- [jsx|<div>x@{return 2}y@{return 3}</div>
instead you'd need
C {x, y} <- [jsx|<div>x@{return 2}y@{return 3}</div>
There are several ways to fix it. The first one is to let the user provide
either a datatype or even a default value, as you suggest in Idea 1
. The
second one is to let TH generate a datatype and corresponding constructor. How
do we do that? Does the user pass in a String that will be the Constructor's
name?
ACons {x, y} <- [jsx|(ACons)<div>x@{return 2}y@{return 3}</div>|]
Either way TH is "happening" outside the quote and either is allowed to modify the environment, like adding new functions into scope, or the user has to somehow leave clues to tell the quasiquote what to generate.
The [jsx|...|]
returns a (heterogeneous) list of all the named values.
However the values are tagged with the name they were given, at the type level:
ex1 :: (MonadWidget t m) => m (HList '[ '(Int, "x")])
ex1 = [jsx|<div>x@{return (1 :: Int)}|]
ex2 :: (MonadWidget t m) => m (HList '[ '(Int, "x"), '(String, "str")])
ex2 = [jsx|<div>x@{return (1 :: Int)}<p>str@{return "1234"}</p>|]
Using type application we can retrieve the values, indexing into the list with Symbol
s:
ex3 :: (MonadWidget t m) => m Int
ex3 = (unWrap @"x" . extract) <$> [jsx|<div>x@{return (1 :: Int)}<p>str@{return "1234"}</p>|]
ex4 :: (MonadWidget t m) => m (Dynamic t Bool)
ex4 = (unWrap @"check" . extract) <$> [jsx|<div>
x@{return anInt}
<p>
check@{_checkbox_value <$> checkbox True def}
</p>
</div>|]
Here's a proof of concept.
It is actually very similar to the tuple idea.
TypeApplications
ex5 = unWrap @"foo" <$> [jsx|<div>foo@{...}</div>|]
[jsx|...|]
block has returned a value
we do not rely on TH anymore. However TypeApplications
is a pretty recent
addition to GHC."foo"
is not contained in
["bar", "baz"]
.To be honest, I really like the last solution. It is type safe and uses non-TH,
standard Haskell mechanisms. We could even provide Proxy
based functions.
Alright, I've made some more progress. There's now better syntax, and I fixed the parser (which I had broken). Also I was wrong yesterday, there are two extensions that you need to enable: TypeApplications
and DataKinds
.
Here's what the syntax looks like:
example :: (MonadWidget t m) => m Int
example = pick @"x" <$> [jsx|<div>x@{return 42}</div>|]
See tests on the branch for more examples. Also, I've moved a project to use this and I've faced no major problem.
The above syntax is definitely more palatable, nice work! I had a few questions about this option:
(pick @"myField" jsxResult)
where you want to use myField
?Also, one comment about TH being "within" or "outside" the quotation. After thinking about it more, I think the most intuitive thing for a jsx block would be to just create local variables in the current scope for each tagged value. Having records generated means now you have to worry about global scope of the field names, which we all know can easily conflict. However, local variables are much less concerning, and I'd argue easy to reason about, even though it technically is modifying the environment. Something like:
[jsx|<div>x@{return 1}y@{return 2}</div>|]
-- now both x and y are in scope
It wouldn't require any exotic data structures, indexing, or boilerplate for the client code, and I think it should be easy to ensure that your tags don't conflict with other variable names in the current do block. I just wonder if this is even possible given the way do
notation sugar works.
Oh, and one other thing about syntax: I am hesitant to put the varName@
outside of the curly braces, as it seems likely to conflict with email addresses. I was tentatively using {varName@someExpression}
. I suppose we should introduce escaping at least for {
and }
, but with the former syntax, we'd also need to do this for @
.
Ok, I've added support for more ways of accessing values. I added two functions to showcase it. In all cases the signature of the jsx
block is m (HList '[...])
and the signature of the jsx'
block is m a
, where a
is the value you want to get. First multi1
:
multi1 :: (MonadWidget t m) => m (Int, String, Double)
multi1 = do
res <- [jsx|<div>x@{return anInt}<p>y@{return aDouble}</p><a><b>str@{return aString}</b></a></div>|]
return ( pick res
, pickProxy (Proxy :: Proxy "str") res
, pick @"y" res )
The first element is accessed using pick
with no annotation. That requires no extension whatsoever (except for QuasiQuotes
, of course). The obvious drawback here is that, if there are two values of the same type, you can't specify which one you want. The second element is accessed using a Proxy
. The only extension that you need is DataKinds
to be able to promote a String
("str"
) to a Symbol
(and QuasiQuotes
, of course). Finally the last element is accessed using TypeApplications
.
Then there's multi2
:
multi2 :: (MonadWidget t m) => m (Int, String, Double)
multi2 = [jsx'|<div>x@{return anInt}<p>y@{return aDouble}</p><a><b>str@{return aString}</b></a></div>|]
That one does not require any extension (except, of course...). However it is restricted to the actual instances you write. However as you can see the instance (for tuples at least) are pretty straightforward (once you understand what Metamorph
does) and could be generated with TH. Another drawback is that the description of Metamorph
needs UndecidableInstances
enabled (in SymIndex.hs
). Not sure that's really needed however.
Regarding adding new variables/functions to the scope using TH: it's the first thing I tried, however at some point I got the feeling that it wasn't possible. My gut tells me that it'll be difficult to put those in scope without making them top-level declarations. Don't trust me on this one, I haven't used TH much.
This is all to say that we can get a long way wihout template haskell with simply leveraging the type-system. It'll probably be more maintainable in the long term as well (which reminds me: QQ.hs
can be fixed so that users don't need to turn on OverloadedStrings
, I've completely failed at updating it).
Ah, regarding parsing: there's also identifier = { ... }
which wouldn't clash with emails (but with some kinds of assignment if there's javascript involved).
@nmattia so I've thought about this for a while - I don't think the syntactic sugar of do notation is extensible in the way we'd want it to be (such that we can introduce new local names into scope without needing it to be i a structure). I like your idea and am inclined to merge it. Honestly at this early stage of the project we can always change things later without worrying too much.
So, do you want to make a PR for this? Regarding syntax, I think a@{expression}
is fine as long as we allow for a\@{expression}
or something to escape the naming. I even think it might be a good idea to introduce some text \{not an expression\}
along with it, but am less concerned about it since that was an existing issue.
Thanks again for all your efforts!
Cool, I'll submit a PR with
Regarding a\@{expression}
I suggest we only focus, in a different PR, on some text \{not an expression\}
. Sounds sensible?
Sounds good to me! Feel free to update the README as well - I'd be looking for some basic examples of this new functionality, but no need for animated gifs or anything :)
Separate PR for escaping {}
is fine by me.
Apologies for the necromancy, @nmattia is it possible for this PR to be submitted, here or my fork? I know it's been a while. Curious about dusting things off. :)
@mankyKitty oh wow! it's been a while, I don't remember much about this. I'm afraid this PR will never see the light of day 😅
Hi @dackerman, first of all thanks for this library! I was surprised on the ease of use, once integrated in my
reflex
project. Hi have a question on the limitation you talked about in thereadme
, though:If I understand correctly, this is only a limitation of the library in its current form, right? What should be done to bind dynamic values out of the node we insert?