Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
What does this pull request do?
Totally rewrites Formless into a single file!
Along the way, I've made several significant usability improvements. The highlights include:
eval action = handleAction handleQuery handleEvent action
awkwardness (no morewrapInputFields
orunwrapOutputFields
, either!)Initial
type class: form fields can have any input, error, or output types you want without restrictionmkSProxies
!)HalogenM
with full access to read and modify the form state (no more specializedValidation
module!) and can be run on change, on blur, or whenever you want!injAction
orinjQuery
-- just write your actions and queries as you would in any other Halogen componentIn sum: forms are easier to write with fewer weird restrictions and much more power. To check it out, please see the examples site and source code.
What are the next steps?
I can use your help! I need people to:
To talk about any of this, please feel free to open issues here, or chat with me on the PureScript chat in the
#halogen
channel, or open discussions on the PureScript forumWhen will Formless 3 be released?
Formless 3 relies on a pull request by @MonoidMusician that implements variant mapping for the
variant
library. Until that pull request is merged, this will remain unreleased and only available via pre-release tags. (Don't worry -- despite being opened in 2018, we've been talking about moving it forward recently!)I would like to use the period from now until release to make other breaking changes and implement feature requests before things get set in stone for Formless 3.
Summary of Changes
Formless has been in an awkward state over the past year or two. After writing halogen-hooks I wanted to rewrite it in Hooks, but the ensuing types didn't feel ergonomic and I dropped the project (you can still see the branch, though!). After some time away I realized there was a way to get rid of all the newtypes and complicated type classes and lenses and proxies and significantly simplify the implementation and use of Formless.
Here are the primary changes:
Move to a higher-order component
I first wrote Formless against Halogen 4. Since then, Halogen has become much nicer for writing higher-order components that transparently pass through queries, inputs, and outputs. Accordingly, the new version of Formless (which works with Halogen 6) is a higher-order component. The higher-order component accepts:
FormConfig
that only requires one field (a way to lift Formless actions into your component action type), but optionally can specify several values to control the behavior of FormlessInitial
instance); most simple forms can just providemempty
for thisThe last bit is the major difference between the old and new Formless.
The old approach was to provide you with a component that already had a form state and an extensible variant of actions for interacting with it. It also already had a
handleAction
for the existing actions, but you could inject your own actions into the variant and then handle them. The component took aSpec
that let you provide your ownhandleAction
,handleQuery
, and so on. You could be notified about events in the form (like submission) by providing ahandleEvent
function to take care of it. The end result worked, but it was clunky.The new approach inverts a few things. Now, you write a component from scratch that is required to accept the form state and actions for interacting with it as component input. When you want to call an action, you
raise
it to Formless for execution. When an event happens in the form, you're notified via a query. Since your component and Formless are entirely separate except for the input/query/output interface, you have a lot more control over your component and it feels much more ergonomic to write.Transfer validation and events to queries
One of the most significant changes in Formless 3 is an inversion of who is responsible for validating fields and handling events.
The old approach was to accept a
handleEvent
function for handling events, and validation functions for each field in the form. When an event arose, or a field was ready to be validated, Formless would call these handlers and then do something with the result. You wrote the handler, but then you handed it off.This works, but it's less powerful than it could be. The new approach is to move events and validation into queries, so that Formless can query your component when an event occurs or validation needs to happen, and then you can handle the event or run validation in your component's
HalogenM
and decide how or if to reply. The mental model is easier: this is normal Halogen parent-child communication. And validation is strictly more powerful: you can now validate inHalogenM
, which means you can access the form state, trigger actions (like setting the values of dependent fields), and more.The new model is powerful enough to match the old Formless functionality without requiring any queries, inputs, or outputs for the new Formless component. In other words, you can write a form component and only accept queries and input relevant to your specific use case. Or you can replicate the old Formless interface, if you wanted!
Remove newtypes
The final major change to Formless regards the form type itself. Formless has always been designed to minimize the number of Form-related types you have to define over and over again, and to be able to transform that input type into several others. But the downside has always been that you can't just describe those transformations with type synonyms (because it would lead to the type synonyms being partially-applied).
The old component focused on your
form
row, passing it various arguments. For example, you could define a form row and then define a type for passing in a record of just inputs like this:This was nice because every derivative type is based off of
form
alone. But the less-nice part was that doing this meant that every derivative type had to be a newtype, since type synonyms can't be partially-applied.But all of these problems go away if you move the types into user-land, so Formless doesn't have any idea you've used a type synonym on your form row to produce a new type -- it just takes an input record of any fields and makes sure they work out:
Formless 3 uses this idea to invert who controls the types the same way it inverts who controls validation. The downside is that you now have to write more types than you used to, but the upside is that you never have to use a newtype,
wrapInputFields
,mkSProxies
, field proxies, or lenses again. Essentially, Formless relies on you for type information and then checks that all your provided types work out together, instead of accepting just the form type and working all the types out for you. I think the tradeoff is absolutely worth it.