We start by importing two type constructor, Just and Nothing, which together form the Maybe monad. After that, we're just importing a set of functions for working with Maybes, and then we're importing a set of general functional programming utilities. All of these things are coming from a library called Sanctuary which is inspired by Haskell.
Below that, we have a collection of data called team points which contains three arrays, each representing a set of points earned by a player on the team. Each value in that array represents the points earned in one of three rounds.
Here we have the points for rounds one, two, and three for player one, the points for rounds one, two, and three for player two, and the points for rounds one, two, and three for player three.
The rules for this game state that players with zero points in any round get disqualified, any earned points greater than or equal to five get doubled, and the overall team score is calculated as the average of all valid scores for each player on the team. This means the teams get pretty heavily penalized when their players score less than five points per round.
Let's start by writing a function which takes in the points a player earned for a round and returns the actual score awarded for that round. Let's call it Maybe score because it will return a Maybe type. The body of this function can be written as a simple ternary expression -- if points is equal to zero, we immediately return a Nothing.
Otherwise, we can use another ternary which checks whether or not points is greater than or equal to five. If it is, we return a Just containing the value of points multiplied by two. If it isn't, we simply return a Just containing only the value of points.
No matter what, this function will return a Maybe which is either a Just of some value or a Nothing which represents no value at all. Before we start using traverse to help solve for the final score of the team, let's apply our Maybe score function to each value for each player and log out the results to better visualize what it does.
Let's just call this result before traversal because we haven't yet used traverse. We can define it by mapping over the team point's outer array, a for each iteration, mapping over the values in each player in our array while applying our Maybe score function.
Now, let's just log out before traversal, and then go ahead and run the file. We can see there's still an outer array containing three inner arrays, but now each value in those inner arrays has been converted into a Maybe.
Each point value for player one has been turned into a Just, and the value for round three has been doubled because it was originally a five. Player two looks a little bit different, containing both Just and Nothing types.
The points for round one have been doubled, going from 6 to 12. The zero for round two has been converted into a Nothing, and the points for round three have also been doubled. Finally, player three looks a lot like player one, containing three Justs and having the round three value doubled.
We won't actually be using this before traversal value directly, but it's helpful to have for comparison to better understand how traverse works. Now let's define a function called apply rules which will be the result of calling traverse with two out of the three arguments it takes.
First, we'll pass a sanctuary's of function. Then we'll pass it Maybe score. Eventually, we'll also pass traverse an array of points for a player. Let's take a moment to explain these arguments. The first argument traverse takes in an applicative which is a super class of monad. In this case, that applicative will be a Maybe.
We could have imported and used the Maybe type here directly. You'll see that in the sanctuary docs, but I use the of function. In this case, it doesn't matter because they would both end up constructing a Just of some value.
The second argument traverse takes is just a mapping function which returns an applicative like how Maybe score returns either a Just or a Nothing. The third argument for traverse is a traversal which is just some sort of data structure which implements a traverse method.
In this case, that data structure is just an array, but we need to traverse over all three players, not just one of them. Let's remove this argument. Now we have a reusable function called apply rules which is based on traverse but is still awaiting the traversal argument.
Let's define a constant called player scores by mapping over our team points outer array and using apply rule to traverse over each inner array. Let's log out player scores just like we previously logged out before traversal and compare them. We'll see that player scores is still an array but each inner array has been turned into a Maybe.
The array of Maybes for player one is now a single Just of an array of points. Because it contained a Nothing, the array of Maybe for player two is now just a single Nothing which we'll use to disqualify this player and the array of Maybes for player three is also a Just of an array of points like player one.
We can see that traverse essentially takes a traversable structures, maps over its values, and transforms each of them into some type of applicative and then flips the type so that, instead of having a traversable of applicatives, we end up with an applicative of traversables.
When working with Maybes, if the result of the mapping function containing a Nothing in any position, the rest of the Maybes are thrown away, leaving us with only a single Nothing. There's actually another function called sequence which is just like traverse only without the mapping step.
In other words, sequence is equivalent to using traverse but passing it a no op or identity function instead of a transformation. If we started with data that looked like what we have with before traversal, we'd use sequence instead of traverse. We can delete before traversal because we no longer need it for anything.
Now, let's continue solving the problem by converting player scores, which contains Maybes, into a set of valid player scores which actually contains values. We can do this by using functional composition. We compost together just and join to create a new function and then pass it player scores.
Just takes an array of Maybes, discards any Maybes of type Nothing it comes across, and simply pulls out the remaining values from Maybes of type Just. That would give use multiple arrays, so we need to use join to merge them together into a single array.
Now, let's log that out just to make sure it's working correctly. It is. We can see that the Just holding the values for player one has been unwrapped and put into the array, the Nothing representing player two has been ignored, and the values within the Just for player three have also made it into the array.
Now, the only thing left to do is to calculate the average of these valid scores to produce the final score for the team. Let's go ahead and define a constant called team score and use composition again to create a new function which first calls sanctuary's mean function on the array to get the average.
Because mean returns a Maybe, we need to then called from Maybe to pull out the value. We pass from Maybe a zero here as a default value to use in case the Maybe it receives is a Nothing.
Now, we should be done solving the problem, so let's just log out scene score to make sure. We are. The team score is six which is the average of the scores for only players one and three because player two was disqualified.