(defn retrieve-subject-items [page per-page sort descending] (take items-per-page (drop (* page-number items-per-page) (sort-by (to-key sort) subject-items))))
Is the “why” important? No. What is important is that all of these list functions have one thing in common:
take = f(int list) => list drop = f(int list) => list sort-by = f(keyword list) => list
The last parameter needed is a list. Mind blown, I know.
This screams for partial functions. What are partial functions? They are functions created to “wrap” existing functions, just with some of the arguments already taken care of. For example:
function drop (numberToDrop listToUse)
Easy enough. It takes in a list, and the number of items needed to be dropped from said list. Now what if you knew that under a certain circumstance, the top 5 will always be dropped. Say there are three lists: MathTestScores, EnglishTestScores, and BiologyTestScores where the “scores” are numbers from 1-100. One rule is no matter which list is used, the top 5 scores will be removed to help with a grading curve. You could do something like this:
drop(5 MathTestScores) ... drop(5 EnglishTestScores) ... drop(5 BiologyTestScores)
As you might notice, there is a pattern here. You could create a method that takes in a list, and removes the top 5. Nothing wrong with that.
Now, as dumb as this might be, there is some reason you’re told to drop 5 from all lists, and add the sum of each list together. Yes, this example is train wreck from a “makes sense” standpoint. Or it sounds like how to calculate a baseball statistic. Either way: Convoluted. But you are merely the worker. Yours is not to ask why.
You might get something like this:
sum = drop5AndSum(MathTestScores 0) sum = drop5AndSum(EnglishTestScores sum) sum = drop5AndSum(BiologyScores sum)
sum = drop5AndSum(BiologyTestScores (drop5AndSum EnglishTestScores (drop5AndSum MathTestScores 0)))
Wonderful. Now lets say you use a language that has method chaining. Basically the preceding method sends its result to the following method. Sort of like the example right above this. Only difference is the order is kept.
sum = drop5AndSum(MathTestScores, 0) -> drop5AndSum(EnglishTestScores, *sumFromLastCall*) -> drop5AndSub(BiologyTestScores, *sumFromLastCall*)
Problem with this is that the drop5AndSum function needs two parameters, but it only produces one. So syntactically you’re &$!@ed. This is where partial methods come in. The goal is to get the call to drop5AndSum to only need one argument: the result from the preceding drop5AndSum function.
sum = drop5AndSum(MathTestScores, 0) -> drop5AndSum(EnglishTestScores) -> drop5AndSum(BiologyTestScores)
BUT HOW?!!? Partial methods… and man whorin’.
partialEnglish = dropAndSum5(EnglishTestScores) sum = drop5AndSum(MathScores, 0) -> partialEnglish
The function the follows “drop5AndSum(MathScores, 0)” no longer needs to have the list passed in. It just needs a number, the sum, and it will do its thing. How is that possible? Look at the original function definition:
drop5AndSum = f(list, int) => int
Now look at partialEnglish
partialEnglish = f(int) => g(list, int) => int
As you can see, partialEnglish takes in the preceding sum result, then applies that to the drop5AndSum function.
Why bother with this? Easier to understand the order of operations.
mathSum = drop5AndSum(MathTestScores); englishSum = drop5AndSum(EnglishTestScores); biologySum = drop5AndSum(BiologyTestScores); sum = 0 -> mathSum(MathTestScores) -> englishSum(EnglishTestScores) -> biologySum(BiologyTestScores)
The intention is a little more readable than the (drop5AndSum … (drop5AndSum … (drop5AndSum …))).
What does this have anything to do with the original block I had at the start:
(defn retrieve-subject-items [page per-page sort descending] (take per-page (drop (* page 5) (sort-by (to-key sort) subject-items))))
Because the order is actually right to left, it may not read well. However, if it read from top to bottom, readability should increase.
(-> subject-items ((partial sort-by :Name)) ((partial drop (* page-number items-per-page)) ((partial take items-per-page)))
Now with somewhat meh keywords ignored (Ahem “partial”): It’s pretty obvious what the order of operations is, as is the overall intent. Start with a list, sort it by name, drop a few, take a few. Despite the ugliness of “partial”, it is still far more readable. Good new is, if you’re using Clojure, you can remove the “partial” part altogether with a macro.
Also if you’re using Clojure, you might notice the double parenthesis. This has to do with the need to resolve the returned partial method in order to apply it to the passed in chain result.