Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract the bits independent of a UI framework? #52

Closed
ajnsit opened this issue Jun 30, 2019 · 8 comments
Closed

Extract the bits independent of a UI framework? #52

ajnsit opened this issue Jun 30, 2019 · 8 comments
Assignees

Comments

@ajnsit
Copy link

ajnsit commented Jun 30, 2019

While porting the library to the Concur UI Framework, I realised that most of the important bits are completely independent of Halogen. So I went ahead and extracted formless completely and created Formless-independent.

I think it might be valuable to have an official package called formless-core or formless-independent for this, and then having framework specific bits in their own packages such as formless-halogen or formless-concur, which would in turn depend on the core library.

What do you think?

@thomashoneyman
Copy link
Owner

@ajnsit This is an interesting idea. I've taken a look at your formless-independent repo and I see how many of the parts of Formless can be independent of the UI library. I'm curious how far that idea can go -- could we put together a version which can be used in react, react-basic, concur, and halogen without requiring a ton of effort implementing each ui-specific version?

Formless might be a bit too big for me to get a handle on that, but I'd like to experiment with making a smaller renderless component and seeing whether it can be extended to cover multiple UI libraries without having to make compromises about how the library functions. On the other hand, it may turn out that a smaller component wouldn't be big enough -- that the advantages of doing this don't show up until there's enough logic outside the component to warrant the effort.

I would love to have the same core code usable in each popular UI library so long as it doesn't make development and maintenance significantly more complex. I'm not sure I'm up to the task of maintaining a constellation of libraries for each renderless component :)

Mind if I get back to you this upcoming work week with a preliminary piece of work that you can help me expand out? Having a small example will make it easier for me to understand the scope of this change.

@thomashoneyman thomashoneyman self-assigned this Jul 7, 2019
@ajnsit
Copy link
Author

ajnsit commented Jul 8, 2019

Yeah that sounds good! I would be happy to help build those examples, and once the core is abstracted, I volunteer to build/maintain the concur and react (and maybe more) specific versions.

@ajnsit
Copy link
Author

ajnsit commented Jul 10, 2019

To demonstrate formless-independent bindings to multiple UI libraries, I went ahead and created examples for Concur and React.

There are two issues so far -

  1. The generic dropdown menu UI component I created (Concur version, and React version) seems to misbehave a bit. This might be a problem with the menu component itself, or a problem with my formless extraction. I will look into this later.

  2. Far too much type level information about the internals of Formless is needed to create the menu component. See all the Cons and Newtype constraints in the type signature for menu. I believe this would be a problem even with the Halogen version of Formless. Is there a way we can avoid it, and still manage to write an abstract component which plugs seamlessly into Formless?

@thomashoneyman
Copy link
Owner

Hi @ajnsit! Thanks for doing this. I've also invited you to a repo tracking a small experiment in expressing common Halogen, React, and React Basic patterns in terms of each other to make sure the consequences of a framework-independent component are well understood. For example, how do you make sure you can handle:

  • Parent-child communication (messages and queries in Halogen, callbacks and refs in React)
  • Rendering (pure state -> HTML in Halogen, state + props -> HTML in React Basic, effectful function in React)
  • Lifecycle events (initialization and finalization are pretty common; but what about matching componentDidUpdate or receive among frameworks?)

With the idea being that you can express an idiomatic component in Halogen, React, React Basic, and Concur if you can translate the reasonably common patterns among them, at which point it becomes really useful to have an abstracted core. I'm feeling more confident that this can be done over time.

I quite like what you've done with formless-independent and I'm glad you were able to quickly get it working with Concur and React. I think the monorepo approach is the right one as well, for maintenance reasons, though it does mean including unnecessary implementations in your project. That said, I believe the compiler and bundlers are able to strip that dead code.

I'm not convinced that the current implementation ticks all the boxes in keeping the core flexible and easy to continue working on without compromising functionality. For example, sometimes you need to imperatively submit a form with SubmitReply because, for example, you've got two forms in the page and one button needs to trigger both of them. That's done using request-style queries in Halogen and I think can be done using refs in React, but it's important to still be able to support those sorts of features.

I'm continuing to work on a much tinier (just an overblown counter) example component which supports the common features, idioms, and lifecycle events of React and Halogen (I'd like to add Concur as well, but I'd need a little help from you I think). That includes documenting how you can write a core which preserves these features which will be useful for future work on other components, too.

Do you mind waiting a little bit for that to reach fruition? I'd like to feel confident that taking this framework-agnostic approach is not going to compromise future development of the core (make some reasonable features unsupportable, etc.). At that point I'd love to work with you on converting the project wholesale to be agnostic and have Halogen be just one of several implementations.


Sorry for such a long message! With regards to the type level information required to create a menu component -- you're right, they're awful. This is from the React menu:

menu
  :: forall opt s form e o restF restI inputs fields
   . IsSymbol s
   => IsOption opt
   => BoundedEnum opt
   => Newtype (form Record F.FormField) (Record fields)
   => Cons s (F.FormField e opt o) restF fields
   => Newtype (form Variant F.InputFunction) (Variant inputs)
   => Cons s (F.InputFunction e opt o) restI inputs
   => form Record F.FormField
  -> SProxy s
  -> (F.Query form -> Effect Unit)
  -> React.ReactClass {}

Most of this is confirmation that the symbol s really is in the form and you're guaranteed that you can retrieve it no matter the form or specified label. I believe most of this can be removed if you have the user provide F.set field directly rather than provide it automatically for them.

@ajnsit
Copy link
Author

ajnsit commented Jul 10, 2019

@thomashoneyman I see an invitation from you, but it gives me a 404 when I try to go to the repo. Is this the right repo name? https://github.com/thomashoneyman/purescript-agnostic-counter

@thomashoneyman
Copy link
Owner

It's https://github.com/thomashoneyman/purescript-agnostic. Let me try again.

@ajnsit
Copy link
Author

ajnsit commented Jul 10, 2019

I can see that repo now, thanks!

@thomashoneyman
Copy link
Owner

@ajnsit I've had some time away from the agnostic backend experiment, and at this point I think it's simply too complex to maintain over time especially as UI libraries continue to diverge (I'm here thinking of React Hooks) and it becomes harder to translate patterns from one library into those used by another. As we were able to demonstrate there, it's possible to create framework-agnostic components, but there are two main issues:

  1. The underlying implementation is so abstract as to be extremely difficult for anyone other than the author to actually contribute concrete Halogen, React, React Basic, Concur, etc. implementations on top of it. A major reason for this is that the component is framework-agnostic and renderless, which puts two reasonably difficult to understand patterns on top of each other.
  2. It was possible at first to write idiomatic code in all UI libraries with the same underlying core implementation. But as I expanded to accommodate more and more features (lifecycle events, extensible state, extensible actions) it became clear that it's not possible to do this. Some sacrifices are necessary to preserve compatibility between UI libraries which consume the core implementation.

I think that library-agnostic components are very much possible to write, but that they're a better fit for application code rather than library code. It simply makes the library too complicated, to the point where I'd expect maintenance to become a serious issue over time.

I'd like to write up the results of the experiment and share them so others can have a go at the same topic -- including a formless-independent, but I don't think that I'll be the one to pull it off at this point.

I'm going to close this issue because I don't plan to release a formless-independent at this time. However, feel free to continue the discussion here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants