(defn get-imcomplete-task-summaries [membername]
(.then (fetch-data)
(fn [data]
(sort-by :time
(for [task (:tasks data)
:when (= membername (:username task))
:when (not (:complete task))]
task ;; immutable, no need to copy
)))))
It makes me a little sad that functional programming has come to mean map/reduce/filter for lots of people, rather than avoiding mutation which is far more interesting.http://smallcultfollowing.com/babysteps/blog/2014/05/13/focu...
I have no idea if this is true, but it's going to be really interesting to see that idea tried.
Readability is in the eye of the beholder. A tidy functional equivalent might look something like this (in a made-up syntax with typical functional idioms):
getIncompleteTaskSummaries membername =
fetchData
>>> tasks
>>> filter (task => username task == membername and not complete task)
>>> map (subsetkeys ["id", "dueDate", "title", "priority"])
>>> sortwithkeyfn getTime
Compare that with the explicit loops and tests above: var getIncompleteTaskSummaries = function(membername) {
return fetchData().then(function(data) {
var incompleteTasks = [];
for (i = 0; i < data.tasks.length; ++i) {
var task = data.tasks[i];
if (task.username == membername && !task.complete) {
incompleteTasks.push({id: task.id, dueDate: task.dueDate, title: task.title, priority: task.priority});
}
}
incompleteTasks.sort(function(a, b) {
return a.getTime() - b.getTime();
});
return incompleteTasks;
});
}
In practice, functions like filter, map, subsetkeys and sortwithkeyfn would probably be in your standard library and immediately familiar to anyone used to programming in such a language, so the only interesting details are the interesting details: which tasks you want to pick out, how you want to summarise them, and how you want the results ordered. Depending on what else is going on, you might even extract some or all of those three details into named one-liner functions to make the main algorithm more self-documenting and/or to allow those concepts to be reused elsewhere.The functional version as shown reduces 10 substantial lines with a lot of temporary variables and deep nesting to 6 total lines that each have a clear, self-contained purpose and that collectively systematically transform your input to your output. To my eyes, that style is far more readable and maintainable than manually reinventing fundamental algorithms like filter and map and camouflaging the interesting details within three or four levels of context.
Edited to add: Often the thing that makes functional style awkward is when a language that wasn’t designed with that style in mind adopts some of the useful concepts but without the elegant syntax and idioms to match. For example, we have somewhat unwieldy notation for defining quick local functions in JavaScript, lambdas that work as long as you can do everything on one line in Python, and the horrific lambda expression notation in modern C++. But I don’t think it’s helpful to conflate this with functional programming style itself, any more than it’s helpful to conflate Java’s verbosity with static type systems.
"you might even extract some or all of those three details into named one-liner functions"
No. Replacing familiar operations with unfamiliar ones (adding a level of indirection) doesn't add readability or make it "more self-documenting" than pure boilerplate code that everyone's seen many times before and will see again.
But these sort of arguments never reach a conclusion. The way to test it would be to teach beginners and see which one is easier to teach. That's a bit beyond a casual internet discussion, though. :-)
FWIW, I'm in the anti-boilerplate camp.