Join egghead, unlock knowledge.

Want more egghead?

This lesson is for members. Join us? Get access to all 3,000+ tutorials + a community with expert developers around the world.

Unlock This Lesson

Already subscribed? Sign In


    Validate All Fields in an Elm Form Using Applicatives

    Enrico BuonannoEnrico Buonanno

    We have run into the problem of of having too many inputs and map2 only being a binary function. We could make a ternary map3 function but eventually we'll run into the same issue.

    In this lesson we'll learn to write a function that will validate an arbitrary number of fields on the form, and compute an overall result that tells you whether the form is valid as a whole.



    Become a Member to view code

    You must be a Member to view code

    Access all courses and lessons, track your progress, gain confidence and expertise.

    Become a Member
    and unlock code for this lesson


    Instructor: 00:01 Let me now add another field to the form. Since the fields we've added so far were both strings, let's do something else and add a field of type int. Let's call this Age. We're asking the user to enter their age into the form.

    00:17 The initial value, of course, will be empty and not validated. Let's have a message, "input age" and let's handle this message here, so when the input age changes, the age on the model goes to "not validated."

    00:45 When we validate the model, we want to validate that the age is an actual number. isNatural is a function that we defined in the previous lesson.

    01:06 Let me create a visual element to represent the age. This would be an input with a placeholder, "your age." This will raise a message of type input age.

    01:19 The current value would be model.age, and we display the value. Here, instead of the identify function, we use toString to convert an int to a string. We'll pass the model.age field to get the proper error, and this is really it.

    01:34 You can see that in the form we have a new field of type age. If you say, "Hello," and submit, we get a validation error here, as well as the previous fields. So far, so good.

    01:49 What we still need to do is to send the age into our message to the back end. Here we would add a new field to our request body with the name age.

    02:00 Here, we would use encode int, age, and we would need an extra parameter age that is of type int. This is where we will have a problem. We were calling map2 with submit as a binary function, and and message as the arguments.

    02:19 Somehow what we'd like to say is now submit takes another parameter. Let me just add this here, model.age. These parentheses are not required.

    02:36 Of course, this also doesn't compile. This is because if I open the definition of map2, you can see it expects a binary function and now we have submit defined as a ternary function.

    02:49 One possibility would be to define a new function map3 that takes a ternary function. Then, of course, we would run into the same problem. We would need to define map4, map5, depending on how many fields we have.

    03:02 Let me introduce a different approach. Imagine that we have a function called Apply. It takes a value of type A, and then it takes a function fromAtoB and returns a B.

    03:19 We could call the first parameter A and the second F. The way to implement this would be to say simply F, A. We'll apply the given function to the given argument, and we get the required results.

    03:30 The interesting thing is that, because of how function application works in Elm, even though we are here defining this as a unary function, this could really be any function. For example, if I had a function F that takes three ints and returns a result of int, also, then I could say take F and apply one, then apply two, and then apply three. If you look at this for a while, this is exactly the same as saying F, one, two, three. Let me remove this.

    04:14 What I want to do is define this function Apply, but in the world of the fields. I'm going to have a field of A, and then the second argument will be a field that contains a function fromAtoB. The return value will be a field of B. Let me call these arguments FofA and FofF, so a field wrapped in an A and a field wrapped in a function F.

    04:39 Let me copy this code here, because the implementation is going to be very similar. I'm going to switch on the case of the field. If the field is valid and it has a value of type A, then I switch on the state of the function. I've decided it will contain a function, and then I can apply the argument A to the function F.

    05:02 I can use this very much like I was doing before with the function that was taking three integers, but in the world of fields. To create a function like this wrapped in a field, I could take the submit function and wrap it into a valid. I will call this function, apply, similarly apply model.message, and apply model.age.

    05:31 I can even delete map2, because it's always possible to achieve the same using the apply function. If I save, this now compiles.

    05:41 Let me enter some data. Let's look at the payload of the submit request. You can see that you indeed have an additional field -- age of 22.