Form validation technique using `Result` (and findings)

TL;DR: If you’re impatient view the working example here and skip to my questions :slight_smile:

How to properly use Result? On reflection, this might’ve been better as an article, so if it’s too long I’ll post things like this someplace else in future — let me know! I’d like some feedback on my form validation method.

Here’s the exec summary of what I learned:

As an example of what I’m trying to achieve (eventually), Bandcamp would be a good example: add/delete/move song, cover art, audio, multiple albums, so on.

Hopefully this helps beginners a bit. Learn from my pain.

  1. Think carefully about your data upfront:
    • 2.00 (Float) is MUCH harder to error check than 2 and 0 Int/Int
    • When using for minutes and seconds
  2. Unpack Maybe in ONE place wherever possible
    • I’m using Result in this case
  3. Splitting out these functions results in better code:
  4. Unbound type variable named Validate a and UserInput a (alias of Result) …
    • MUST provide String or Int wherever it’s used
    • UserInput a doesn’t represent the input type …
    • It represents the Ok data type
  5. Trying to chain Result with different data types is probably a bad idea
  6. Nested records are probably discouraged in Elm (for whatever reasons)
  7. Destructuring data types is really handy in functions and makes them simpler!

The problem

A simple form with errors that creates a custom type.
Using a Result to validate forms and translate it into a Song type.

I understand there’s many ways to validate a form, so that’s not really my question … it’s more the stuff that I cover in this thread that’s tripping me up. I think a lot of beginners to intermediates will suffer the same fate!

I think it’s sometimes good to STOP and reassess your decisions, let the background brain take over (sleep on it), or understand how you might write code in a better way first.

My initial (failed) attempt

I tried to chain Results in my first attempt.

Input -> ClickedSave -> Result
                        |_ Chain Results
                           |_ Err? -> Return "Error" in view
                           |_ Ok?  -> Return "Ok String"
                                            |_ check if error -> update Model

In Update Model we’d either create an Album Song [] or if we already have an album, update the Album Song (List Song). You could’ve also generated a Song with that Ok if no errors.

I got a bit stuck with that method as the Result chaining felt a bit messy, and I’m not even sure if it’s supposed to be used in this fashion (the Elm Guide example uses a single data type for Result and Result.andThen).

Hard questions that tutorials don’t always help with:

  1. What model do I use?
  2. How do I use that function properly?
  3. How does the data flow? How do functions interact?
  4. Where do I go if I get stuck?

Tutorials are great, but learning enough to be able to build things alone and figure stuff out if you don’t know is hard to teach I guess.

Second attempt — :star_struck: success!

Anyways, for my second attempt I decided to just give every input it’s own Result, change the model to UserInput records, and use those results to format the data (if needed) or return an error for that field:

Input -> { m | valid = Result String a } -> Render view for errors

Turns out this is a bit easier. I’m currently updating the Result for each field on user input. I’m not sure if this is the ideal way to do things. After reading @rtfeldman reply here it seems clear that your update functions are the only things that should be dealing with state. Msg are just simple containers for data, right?

ClickedSave -> case runErrorsAndBuildSong in update
                 |_ Nothing -> return model (show errors in view)
                 |_ Just song -> update model -> show the album in view

When the user clicks save, we need to cycle through the .valid field in each user input, and my first thought was to reach for List.map … but it seems types in Elm are way more fussy than I thought:

{ valid = Ok 20 }
{ valid = Ok "Get Back" }

Throws an error. I was quite surprised. So you can’t even have different Ok data types when trying to use a List.map. Hmm.

This leaves me with SO many questions

Feel free to just answer any (or no) question you care to!
If you’re interested, see this post for the general problems I’m having (and assume many reading this will have)

  1. Are my functions in the right place?
  2. Is validating on ClickedSave preferable to EnteredInput?
  3. I thought it might be useful to have two Msg for button action:
    • ClickedSave (check errors, then handover to …)
    • SaveSong (handle the update album function)
    • is this a silly idea?
  4. How can I improve the code base so it’s scalable?[1] (A bigger form)
    • Narrowing the types
    • Splitting out the Msg (isolate the form)
    • Better ways to check all errors for lots of fields?
      • Alternative for List.map .validate [...]?
  5. Am I dealing with multiple form inputs correctly?
  6. Should I avoid nested records completely? (A flatter style)
  7. Anything else I could improve?

So yeah, I continually find that programming is hard, and often your program asks more questions than it answers!


  1. I’ve watched, but don’t fully understand “Scaling Elm Apps”, and the Elm Spa example is too big for me to follow. ↩︎

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.