Most apps these days have a sequence of screens that gather information from the user, like a registration flow, a form of some kind. The data from each step is typically combined into a single data structure. For example, let’s say we want the name, age, and the password to authenticate the user.
One way to model it is by using the following data structure:
1 2 3 4 5
One issue we are going to come about is that our model is strict, it needs all the values at once, whereas users will supply each value at a time. First they will type in their name, then their age, and so on.
Wrapping up the fields in
Optional, may loosen its strictness.
1 2 3 4 5
Our flow code might look like:
1 2 3 4 5 6 7 8 9 10 11 12 13
However, now we need to
guard against any
nil values if we want to use them (for example, to make a network request).
1 2 3 4 5 6 7
From a domain perspective, that
return doesn’t make any sense.
One could argue that it’s “safe” to force unwrap in this case, or that there is already a nice approach to this problem.
One may say, “we can raise an error to the user” or “we could track it and check if users are getting stuck somehow”. But, at the end of the day, this is not a good solution because you know that when the flow ends, you have all the values.
Our model is “lying” to us. That’s not loosen, it’s just flawed.
There are several approaches to make it better, like “one model per step”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
That’s better! But there is also another way of doing things that doesn’t involve duplication nor partial data structs.
Instead of breaking down our data structure, why not to break down functions?
FormData initializer, when interpreted as a function, has this shape:
But we can break it down into plain old lambdas1, and by applying it to the initializer for our data structure:
This technique is called currying. What it does is, it allow us to translate the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument.
1 2 3 4 5
The function above goes from a function that takes multiple arguments
(A, B, C) and produces a
D, to single functions, that take one argument each:
(A) -> (B) -> (C) and produces a
D, making it possible to partially apply each argument, one at the time, until it can evaluate and return the output value.
Using it in our flow, may look like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
I’ve added a few
type aliases just to make it more readable.
Cleaning up them further, we’ll have:
1 2 3
If you ask me, this is much better because we didn’t have to write anything else, other than the
curry2 function itself, which can be used in other places.
And that’s it! Functions have saved the day :)