Comment by mehulashah
16 hours ago
Tail recursion is beautifully deep and simple. It (and as a corollary CPS) makes clear what state matters to your loop (and function) and avoids extraneous variables in loops as well as implicit unneeded state in functions. It also makes it easier to show the correctness of your loops. Sure, there are other functional language constructs like fold and map, if your problem is amenable to them. Tail recursion is more general and simpler.
And a must-have in Erlang/OTP (and Elixir of course) where you can "loop" passing the altered state as a parameter. I love it. And wish that all languages implement TCO. That's one of the reason I also like Scheme.
I have almost the opposite view: tail calls are great.
But most of the time you will want to express your program in terms of more restricted combinators, because restriction makes the readers job easier. Easier to understand, and easier to convince yourself that no weird things are happening.
Basically, you might already agree that restricting mutation is good. This is the same principle.
So when you see a 'fold' you know even without looking at the callback, that your program will run through the whole list and not exit early. When you see a 'map' you also know that no early exit will happen, but even more you know exactly how the return value will be constructed (whereas for fold that's arbitrary).
However you are right that 'fold' and 'filter' and 'map', just like 'for' and 'while', are not good as universal building blocks.
That's why you should define new combinators when you need them. Typically, that's when you define new data structures, eg when you define a tree you will also want to define a 'map' for it, and also various tree traversals and perhaps searches. Less typically, you also want new combinators for new algorithmic ideas, even on old data structures.
Eg matrix multiplication is an interesting combinator. You provide the meaning of 'addition' and 'multiplication' as call-backs, and depending on your choice you get an algorithm for finding shortest paths or for matching regular expressions etc (in addition to the obvious matrix-multiplication on real numbers, of course). See https://news.ycombinator.com/item?id=43076088 for the former.
I learned recursion in Pascal and had a ball implementing innumerable algorithms purely in recursive structures. The code was often more compact and simpler to explain — once you understood induction. I agree that combinators are helpful, but they’re not universal. Anyway, we’re probably violently agreeing.
Yes, Pascal (as least the version I know) weren't exactly friendly to passing around first class functions. It might be possible, but it ain't pretty.
I'm not sure Pascal does closure well?
A combinator heavy style works best in a language (and ecosystem) that welcomes it, like eg Haskell.