Part of what makes Clojure a great programming language is that you don't have to believe this if you don't want to. Nobody has yet convinced me that recursion has any sustained advantage over looping.
Using a loop is generally bad practice if a more specialised operation is available (don't loop if something is a simple map or reduce for example). But if the situation justifies a recursion then it usually justifies a loop unless the recursion is particularly neat. Recursion has the same problem as looping - it doesn't tell anyone anything about what the code is really doing. If I see map then I have implicit and explicit expectations about what is about to happen.
I enjoyed the article though.
When I program in Haskell, I rarely use recursion myself. Most loops are just maps and folds. Need to build another structure from existing structure? Foldl'. Once I understood that the value being folded can be arbitrarily complex, FP became simpler (but terser) than iterative programming for me.
When learning them coming from an imperative background, it's inevitable to start with that idea, but somehow feels like a limiting model.
Small example, though not exactly comparable to looping.
I needed to create a human readable qualified path appended by a hash (to avoid conflicts). This qualified path was needed ahead of the construction of an object, and the logical name must have been used in both the hash and qualified path as it was global (Yes, it was an S3 bucket).
I wrote a function
uniqueQualifiedName: Construct x Optional(logicalId) x Properties -> string.
The recursive step is at the top, where the logicalId is transformed to a construct in the scope of the first argument, and the function is called again with NewConstruct x Nothing x Properties. This could easily be adjusted to work with a list of logicalIds instead.The invariant in the recursive step is that once the call returns, the construct at the end of the chain will be exactly the construct I inserted, and I can pop it safely, and the scope object will be none the wiser.
The invariant in the base is simply that I don't need to worry about logicalId being anything.
You can see then how easy it is to adapt this to work with a list, whereas with a loop you'd have to write it from zero, and then roll back. Too messy imho.
But it sounds like you could just bash the logicalIds together and append the hash of the bash of the mash. Might not need loops or recursion.
Which isn't quite the free for all it is in traditional languages
You have to be very conscious and coding against the grain to use it to bang on values in place
I'm glad you enjoyed the read though :)
Unfortunately in most languages this pattern will lead to stack overflow on medium-sized inputs, so you can't often use it unless you're using a language like Racket or Erlang which can't really stack overflow.
Looping may require trampolining or defunctionalisation, whilst recursion can be written much more directly and simply. As a very simple example (in pseudocode):
even(n: uint): boolean = n match {
case 0: true
case n: odd(n-1)
}
odd(n: uint): boolean = n match {
case 0: false
case n: even(n-1)
}
Whilst these are pretty silly implementations of odd & even, I don't know of a direct analogue using loops. For more realistic examples, just look at any "main loop"; AKA "trampoline boilerplate to work-around our language's lack of tail-call elimination" ;)A closer analogy to my code would be something like this: the domain logic is still abstracted and encapsulated into separate units; the relationships between those units are preserved (e.g. if we set a breakpoint in `even_step`, it will be triggered by `odd`); etc. However, the loop here is literally just a trampoline for thunks, which makes it highly non-idiomatic for imperative/looping style:
type STEP[T] = either[T, unit => STEP[T]]
stepper[T](current: STEP[T]): T = {
for (result = current; result.isRight(); result = result.value(unit))
return result
}
even(n: uint): boolean = stepper(n match {
case 0: left(true)
case n: right(() => odd(n-1))
})
odd(n: uint): boolean = stepper(n match {
case 0: left(false)
case n: right(() => even(n-1))
})
Instead, we could defunctionalise; but that requires some separate data structure and "interpreter": type STEP = ODD(n: uint) | EVEN(n: uint) | RETURN(x: boolean)
even_impl(n: uint): STEP = n match {
case 0: RETURN(true)
case n: ODD(n-1)
}
odd_impl(n: uint): STEP = n match {
case 0: RETURN(false)
case n: EVEN(n-1)
}
interpret(current: STEP): boolean = {
while (!current.isReturn) {
current = current match {
case ODD(n): odd_impl(n)
case EVEN(n): even_impl(n)
}
}
return current.x
}
odd(n: uint): boolean = interpret(ODD(n))
even(n: uint): boolean = interpret(EVEN(n)) for(range(n)):
is_true = !is_true
And adjust for all the off by 1 errors. You're managing the same amount of state both ways, but with the loop all the state mutation lives on one line instead of spread throughout a stack. bool odd(uint n){return n&1;} bool even(uint n){ return !odd(n);}
I can't help but think in terms of classic CPU. #include <algorithm>
template <typename T>
bool even(T n)
{
bool is_even{true}, is_odd{false};
for (; n--;)
{
std::swap(is_even, is_odd);
}
return is_even;
} boolean isEven(int n) {
boolean even = true;
for (; n > 0; --n) even = !even;
return even;
}* https://www.infoq.com/presentations/Thinking-Parallel-Progra...
most sql systems support recursive queries. I believe recursive code can be analyzed by the system and executed in a the most efficient manner.
Loops have sideffects closely linking them to actual execution, which makes them blackboxes to the system.
https://www.postgresql.org/docs/current/queries-with.html#id...
Note how there is no way to remove a row from the result set once it has been added. That would not be the case with a truly recursive query, because you would be constructing a new result set at every step. As a concrete example, try to write a graph query which finds all nodes exactly three edges from some starting node. That would be trivial with true recursion, but is impossible with a recursive CTE alone.
Maybe partly because ordered sets are often implemented as trees, which are definitely easier to traverse recursively.
I find recursion nicer to work with mentally; continuous rather than discrete, with less edge cases to consider.
Recursion gives you a stack by default. You don't have to explicitly think about the stack. In looping the stack must be explicit.
Recursion and looping are the same thing. Recursion can be mechanically translated to a for loop and a stack, the concepts are isomorphic.
Hell yeah! The problem with "functional" programming is that a lot of people simply don't get you want to push as much of your program to be generic tools that transform things (ie FUNCTIONS!) When I start on a new problem I typically look at what kind of tools (functions) would make the solution concise and readable. Then create the tools and then write the solution with the tools.
Unfortunately most developers come with preconceived notions of how the program should be laid out and what the development process should be. If they come from OOP world, you will see code that pretty much looks like objects just without OOP machinery. If they come from scripting/procedural then you will see long stretches of instructions just split into smaller "functions".
> Recursion over Looping
Recursion is elegant but it is also hard to understand for many people. I object putting anything into code when the only purpose is making the code look elegant / more advanced at the cost of narrowing audience that can effectively work with it.
My goal is to make the code stupid simple. My main challenge is preventing my ego from trying to impress the reader.
Frequently recursion is more readable than looping. I don't hesitate using recursion in that case. But I make sure that the reader should be able to instantly recognise the pattern and the pattern is not too complex.
Another thing to push recursion to be more manageable is just structuring your code to extract recursion from everything else. Make one or two very small functions that are only responsible for recursion, extract all other logic into other functions that are meaningful on its own (and just happen to be used in recursive context).
If the recursion would be too complex, usually iteration is going to be easier to represent the solution in manageable chunks without requiring the reader to take everything in in one go.
Corecursion, aka literally just plain looping, is a lot simpler and better for many, many activities.
If we taught recursion by contrast, then it makes sense.
Perhaps? But the reason I reach for recursion is so that I don't have to first produce the "wrong answer", and then modify it until it's the "right answer". The sum of 1..10 is always 55. It isn't first 0, then 1, then 3, then 6, etc.
Sums and factorials are the worst possible way to teach recursion because they look like a loop with a weird twist that adds conceptual complexity but doesn't seem to be there for any other reason.
Recursion should really be taught by application to complex nested data structures. It's a lot clearer when you apply it to structures with multiple sub-levels that can be processed identically than with a simple linear list or range.
Recursion is also producing results that are "wrong answers" although I prefer to call them partial results. Just like a loop that is producing partial results as long as it has not finished yet.
When you have a language and context that makes recursion easier to understand than loop -- go for recursion.
One other reason to go for loops is to make sure you control the stack. With recursion it is not always immediately clear that the loop is going to get tail call optimisation. And good 9/10ths of developers meet me with a blank stare when I mention it. At least with a loop it is clearly visible how much space and in what way you are allocating. I had one dev who said he likes recursion because he says it is more memory efficient. To which I had to point out that each level of recursion creates a new stack frame. The guy just wasn't aware of it...
Yes, a lot of people seem to have trouble with it, but it isn’t fundamentally more complex or difficult. The hardest thing about learning recursion is unlearning the other patterns. It’s totally a matter of familiarity.
They occupy the same space as Java's streams, but manage to do the same stuff with a smaller, more generic, more extensible interface, that can do more stuff (eg intermediate stages get told when input is finished). I'm jealous.
EDIT: IIUC, in Java terms, Clojure's "reducing functions" are like Collector, and a transducer is a function which takes a Collector and returns another one. There is then a little bit of top-level sugar so you can give a sequence of transducers, terminating in a concrete reducing function, and get back a reducing function which itself feeds things through the chain of reducing functions built by the transducers. You could probably replicate this in Java, but it might be too clunky even for Java programmers.
https://gist.github.com/tomwhoiscontrary/1d4799fb85b4890a96e...
They were an small obsession of mine last week as I wrote an accompanying blog post to demystify them.
Personally, i would say that a blog post which starts "We can think of a transducer as a context-independent transformation composed of, say, many reducers" and then starts adding parentheses is not really demystifying. But perhaps i am not the target audience.
I don't want to need to think hard about what code does, it should be clear. Or jump through 100 files to find a simple algorithm that has been divided into 100 pieces.
I think people refactor to their own understanding or mental model or refactor-to-understand.
I think there are cases where imperative code is intuitive and others where functional code is intuitive.
I've worked on two professional projects in Clojure at a surface level but I still find Python easier to read, but that's my experience YMMV.
There's a point in my programming projects when my own understandability is weakened and I this week I've been trying to think of a mindset that simplifies the problem. I am experimenting with multithreaded code in Java that implements left-right concurrency control. The idea is readers don't block writers and writers don't block readers and writers don't block writers. Give each thread a shard and rely on commutative property of your tree data structure and have a coordinator thread handle merging. The benefit: each thread can operate at single core speeds without any synchronization for reading OR writing. Buffer flipping is handled by the coordinator thread.
Each thread has its own copy of state which is merged by the coordinating thread. So it's an eventually consistent system. The result: each thread can modify its own copy of the data as much as it wants and it can see its own snapshot of global state at the last snapshot point. Cross thread writes always happen on the inactive buffer.
How do you think about data transformation pipelines? If only there was an IDE for kafka or clojure transformation pipelines.
Although, I disagree about Clojure's readability, but that's probably because I've been doing it for so long.
Interesting Java project you've got there. Reminds me of Clojure's core.async library and software transaction memory ;) (though, I know it's not exactly the same thing).
If I was transforming large streaming input from Kafka or Kinesis, I would probably reach for Cortex (a map-reduce style lib for Clojure), or I might rig up some strange, stream-to-lazy-seq adapter and reach for transducers as they have much better performance when dealing with larger input.
https://github.com/joeduffy/joeduffy.github.io/blob/master/_...
I like left-right concurrency control because it sidesteps a number of thread safety problems by ensuring that a thread can always safely read or write to its own buffer.
Sorry for the confusion.
Recursion is a purely math concept, in real life the chips don't work with recursion, they work strictly with if-then (more current/less current) logic. Assembly language is also procedural.
Also, there is another fundamental thing that is a blocker for functional programming - our brain. For recursion you have to keep in brain many things while you call yourself. This is extremely hard (hmm, I wonder why the Lisp Machine was so pricey ;) ) - especially for the majority of people whose brain has problem to remember more than 2 things in the span of 5 seconds.
If you like math, yeah, sure use Haskell or Clojure, but for the most people it's an overkill and for the most part people still tend to write very procedural code - you have for loops in Clojure - I am wondering why ;D
I like some things that are often associated with functional programming, e.g. immutability, but I am not a fan of recursion at all. Also there seem to be some obsession of having dynamic types in many Lisp and functional programming languages, for example Clojure or Elixir, and I hate it.
it depends on your problem domain. Recursion makes some things easier - much easier than iteration. Try solving tower of hanoi without using recursion and see how hard it is: https://www.geeksforgeeks.org/iterative-tower-of-hanoi/
Or try writing a merge sort algorithm without using recursion.
For instance, one of the sections in this blog post is titled "Functions over Objects" but any Clojure programmer with experience will agree that most code ends up becoming some sort of map munging. Rather than "functions over objects" you really have "map-munging and function instrumentation in dev over banging on concrete instances of classes".
There is also this statement
> functional programming languages often facilitate iteration through recursion
then the author proceeds to mention loop/recur, which is nice, but it is dishonest at best and lying at worst to imply loop/recur is the most natural or common way we iterate over a structure. Off the top of my head, I always see idioms like `(for [[k v] some-map] (do things with k v))`. It would also be nice to demonstrate how faux-recursive looks like with loop/recur rather than stating Clojure's lack of TCO and not elaborating further on how the idiomatic version of `recursive-map` would look like.
Next,
> by (nearly) eliminating side-effects, functional programming (nearly) elminates this whole class of bugs.
That may be the intent of purely functional code in general, but in Clojure one regularly interoperates with the host, to take advantage of their rich ecosystem. Many of the libraries one will interop with will involve some imperative or stateful things! So I think it'd be better if the author had mentioned the idea of "functional core, imperative shell." That is a pattern most Clojurists would agree with, and it is what people really do in a code base, or at least attempt to design.
Lastly, while Clojure allows one very naturally to mock up a finite state machine with a single map and writing a few functions to describe transitions given the current state of the machine, I want to emphasize that not much Clojure codebases I've seen ever used FSMs explicitly :-)
Overall, the article is okay, but my brain registers it as another "Clojure portrayed with rose-tinted glasses and contrived examples" article. Not that this is bad. It's just not the kind of sales pitch I'd want to show to non-Clojure programmers, because they will likely want to ask questions about maintainability, testing, instrumentation, etc. And it is possible to show Clojure being nice for each of these things. At a previous job I got to see ClojureScript code dating back to 2014, untouched, and surviving 8 years worth of language and library updates.