TL;DR: If you’re impatient view the working example here and skip to my questions
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.
- Think carefully about your data upfront:
- 2.00 (
Float
) is MUCH harder to error check than2
and0
Int/Int
- When using for
minutes
andseconds
- 2.00 (
- Unpack
Maybe
in ONE place wherever possible- I’m using
Result
in this case
- I’m using
- Splitting out these functions results in better code:
- Build
Song
function (in thecase
statement) - Create
Album
function (passing it aSong
)
- Build
- Unbound type variable named
Validate a
andUserInput a
(alias ofResult
) …- MUST provide
String
orInt
wherever it’s used UserInput a
doesn’t represent the input type …- It represents the
Ok data
type
- MUST provide
- Trying to chain
Result
with different data types is probably a bad idea - Nested records are probably discouraged in Elm (for whatever reasons)
- 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 aResult
to validate forms and translate it into aSong
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 Result
s 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:
- What model do I use?
- How do I use that function properly?
- How does the data flow? How do functions interact?
- 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 — 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)
- Are my functions in the right place?
- Is validating on
ClickedSave
preferable toEnteredInput
? - 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?
- 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 [...]
?
- Alternative for
- Am I dealing with multiple form inputs correctly?
- Should I avoid nested records completely? (A flatter style)
- Anything else I could improve?
So yeah, I continually find that programming is hard, and often your program asks more questions than it answers!
I’ve watched, but don’t fully understand “Scaling Elm Apps”, and the Elm Spa example is too big for me to follow. ↩︎