Instead of looping from 1 to 5000, you define the relationship instead:
sum(1,n) = 1 + n + sum(2, n-1).
Isn't this clearer to understand problem first, instead of just looping ?
The concept of a loop (i.e. doing something over and over as long as a condition is true) is (again IMHO) a much simpler concept than recursion.
The university I worked at tried moving functional programming into the first semester. Let's just say, that didn't work out at all. The imperative programming style is apparently much easier for students to grasp.
Edit: The more I think about it, it seems to me that a loop is clearer because the initial state, upon which you iterate on, is much more obvious. Where with recursion, I primarily see the step, but not where exactly I started from -- if that makes any sense..
Instead of looping from 1 to 5000, you compute (1 + 5000) * 5000 / 2
This is what "understand problem first" actually means
sum(n) = is_even(n) ? n/2 * (n+1) : (n+1)/2 * n
because of one n and n+1 will always be even. But it show that unfortunately things are often not as straightforward as they first appear. sum(n, acc=0) = n == 0 ? acc : sum(n-1, acc+1)
To me that negates much of the 'declarative' character of a functional style. I don't feel it's really clearer than the imperative for-loop acc = 0
for i = 1 to n
acc += 1 sum(0) = 0
when n != 0: sum(n) = n + sum(n-1)
is kind of clearer. But quite often it is not that easy to give an recursive solution for an iterative problem. When you want to change every even number in an array/list from n to n+1 you can somehow do it recursively, but it is mor obvious to iterate over the elements (which in many functional languages can be expressed by "apply" or some similar function).sum(1,n) = n*(n+1)/2
Which some compilers will produce for you, even if you use loops.
Maybe if you chose a different example, like generating power sets or the Fibonacci sequence, your point would be better made?
some people think in both. they should write blogs and help the whats and the hows communicate.
Functional programming is fundamentally at odds with imperative constructs. It's not that people hate them, it's that they generally just don't fit the paradigm of "build a pipeline of steps (functions) and feed data into it" as well. Most of my software is functional, but you will find the occasional C-style loop, especially if the map/filter/reduce alternative is ridiculously complicated to read.
Arrows in Haskell are actually pretty good at building functional left to right data processing pipelines.
---
[1] https://en.wikibooks.org/wiki/Haskell/Understanding_arrows
I had thought tail-call optimization was a way to get inefficient recursion to parity with iteration, by turning it into iteration. I'd never heard of recursion being faster on its own.
Is it a javascript-specific thing due to how their int types work?
1rst one: It really depends on your data structure. If you use a tree, recursion will be faster on most operations (unless you optimize like crazy, but tbh, it's never worth it). Even with simple linked lists, some operations are better written with recursion.
2nd: more often than not, using first order functions will be both more efficient and easier to read than a for loop. If you're using a foreach loop to apply a function to each items, please use a map.
So something like:
bool done = false;
while (!done) {
...
}
makes no sense unless you have some way of setting `done = true`; which is not possible in purely functional languages like Haskell.The really funny thing is that the while loop itself is not built-in to Haskell but you can write it yourself as a function. In other languages (non-lazy) this sort of thing is not possible without resorting to macros or other tricks, because laziness is required in order to define the correct semantics for conditionals.
One is through constructs like Clojure's `take-while`. Instead of setting a vairable when you want to exit the loop, you define a predicate that is false when the loop should exit.
Another is through `any` and `all` (aka `some` and `every`), which work in a similar way to `take-while`, except they reduce OR or AND across the returned values from the predicate as they go.
EDIT: grammar and clarity
Nothing beats fitting a week of groceries on your bike, though.
Cargo bikes don't get stuck in traffic; functional programmers don't like getting stuck debugging.
Cargo bikes are easy to park; functional programs are easy to run in a REPL.
...and so on.
Loops conflate two different questions: "what do you want?" and "how do you want it done?" If I write (Haskell):
map (+1) list_of_numbers
I have expressed only that I want one added to every number in a list. Whereas if I write (C): for (size_t i = 0; i < sizeof(array_of_numbers); i++) array_of_numbers[i]++;
I have expressed both that I want one added to every number in the array and I have also described exactly how to do it, including the order I want it done in.So what's the big deal? On the one hand, the imperative approach gives me more control. On the other hand, I may not want that control! Perhaps I don't care what order the numbers are incremented in, I just want it done, and the extra details in the for loop are just "line noise" which gets in the way of being able to quickly read the code and understand what it does.
There are other factors, of course. One advantage to the functional approach is that the library author who supplies map (since it's not built-in to the language) is free to change the details under the covers. Perhaps they find a clever way to parallelize the implementation, then all of my code which uses map gets a free speed-up! Now, there may be other reasons why this won't work in practice (to do with legacy code, of course) but the principle is sound!
There are many religions in programming world
Just get familiar with them, take what is sane and avoid the rest
Extremas are rarely the best
Well, they are either the best or the worst (≧▽≦)
A loop is a label+goto in disguise. You have to look at what's inside to understand what the loop is doing; you can't get a global sense of how it is impacting data without looking at what it actually does.
In functional programming, loops are replaced by constructs such as foreach, reduce, map or filter, to indicate right away how the body of the loop operate on the data it is scanning. It enables a functional approach to looping, where one first thinks about how data are transformed -- about the functions to apply on items and what is the overall output of the loop.
Instead of using loops, we often use higher-level functions like map, filter, and reduce. These functions allow us to focus more on the "what" (the operation being performed) rather than the "how" (the control flow). It's like saying, "transform each item in this list," as opposed to, "start here, do this to each item until you get to the end."
And when we do need to perform repetitive operations, we tend to use recursion rather than loops. Recursive solutions don't require mutable state and are easier to reason about, especially when the logic gets complex.
Lastly, mutable state, which is common in loops, can lead to issues such as race conditions in multithreading or distributed computation environments. The minimized state changes in functional programming makes it often more suitable for these scenarios.
So, it's not really about "hating" loops, but more about choosing the constructs that align better with the philosophy and goals of the functional paradigm.
To conclude, In order to use imperative looping constructs, you have to violate referential transparency and immutability, which are the hallmarks of functional programming.
https://en.wikipedia.org/wiki/Referential_transparency?usesk...
https://en.wikipedia.org/w/index.php?title=Immutable_object&...
The former prefers a "declaration" of THE state. for/while loops imply a change of state, so they're a big no here.
The latter prefers an "imperative set of steps" to go from initial state to the desired state. for/while loops make the "steps" easier here.
I don't 'hate' loops, I still use them sometimes. Personally I just try to avoid them (in JS) because I feel like I can solve my problem without any side-effects. It's something less to think about. It's nice when the logic/variables for a function are encapsulated entirely inside that function. It also makes it easy to extract functions so you can re-use them elsewhere.
2. Composition: If the loops don't mutate there are functional alternatives (map, reduce/fold, filter, etc) which are composable.
3. Abstraction: Loops are traversal instruments. Sophisticated functional languages / environments have some mechanism for generalizing traversal of any data structure into a higher order function or some form of reusable generalization.
That said, I use a lot of loops in library code for performance and expose functional interfaces for consumers.
What's interesting is that you start to see less need for side effects in most cases. Still some, but a lot less than you'd think.
FP is built on theory. The language constructs follow the theory. Part of the theory is algebraic data types which allow for structural recursion. Fancy words that basically come to mean the structure of the code that manipulates data follows the structure of the data it manipulates.
Take a loop typical loop
var someResult = 0;
for(i = 0; i < whatever; i++) { someResult := updateResult(i, someResult); }
This uses the natural numbers (integers 0, 1, 2, ...) [Some people define the naturals to start at 1. This isn't important.]The natural numbers are an algebraic data type and therefore allows for structural recursion. For the natural numbers the structure is, a natural number N is either:
* 0; or
* 1 + M, where M is a natural number
So you write out your structural recursion skeleton
def loop(n) =
n match {
case 0 =>
case n => loop(n - 1)
}
This exactly follows the structure of the data, including the recursion.Then you fill in the missing pieces
def loop(n) =
n match {
case 0 => 0
case n => updateResult(n, loop(n - 1))
}
Job done. The theory guides us to the solution. No loop needed.A lot more here: https://www.creativescala.org/creative-scala/recursion/
The main other reason is equational reasoning aka reasoning using substitution. Described here: https://www.creativescala.org/creative-scala/substitution/
This construct is natural with functional programming's abstraction: the function. You encapsulate basic behavior in functions, and then shape the data flow with map(fn)/reduce()/flatmap(). If you really need an index, you can still have them with number ranges, zipWithIndex, etc. If you do use them, you are explicitly reintroducing ordering into your program and lose the parallelism.
This is just a finer level of detail wrt accidental vs essential complexity. IMHO it is always better to specify less about your program, and stick to what you must encode and only that.
The latter is declarative and more expressive and when you compose these pieces, you express your code at a higher level of abstraction as a whole. Maintainability, fewer scope for bugs etc.
oh and loops constructs are not composable...
It's about what conveys the intent most clearly.
What do you prefer?
``` let sum = 0; for (let elem of array) { sum += elem.value } return sum ```
Or
``` Math.sum(...array.map(elem => elem.value)) ```
What about
``` let parent = document.createElement("div") for (let user of users) { let userElem = renderUser(user) parent.appendChild(userElem) } return parent ```
vs
``` let parent = document.createElement("div") parent.appendChild(...users.map(renderUser)) ``` vs ``` <div> {users.map(user => <User {user} />)} </div> ```
For example, (I've spent a lot of time in C#,) let's say you have a collection of Foo objects, but you need to convert them to Bar objects. There's two general patterns in C# to do this:
var bars = new List<Bar>();
foreach (var foo in foos)
{
bars.Add(foo.ConvertToBar());
}
Versus: var bars = foos.Select(foo => foo.ConvertToBar());
Which one is more readable? If you're a newcomer to the language, or you generally haven't worked with "Select" before, the foreach approach might make more sense.But, as you get used to "Select" and understand what it does, you'll probably come to the conclusion that foos.Select is easier to read, especially if you're looking at code that someone else wrote, where you might not have all of its context in your head.
Some other responses in this thread explain the difference by saying that "loops are saying how you want something done, functional is saying what you want done." This also is helpful when reading code where you don't have all the context in your head. (IE, code someone else wrote, or code you haven't worked with in a long time.)
To provide an example, you could have an array, int[] in C#:
var sum = 0;
foreach (var i in values)
{
sum = sum + 1;
}
Or: var sum = values.Sum();
Both are valid C#. But, the functional approach is much easier to read, because you don't need to understand as much context when comparing the loop approach to using the Sum() method.If you're a novice to C#, a novice programmer in general, or you're merely unfamiliar with Linq, the loop approach might seem easier. It's "not wrong..." But, remember, you're saying "how" instead of "what."
A codebase that constantly says "how" instead of "what" ultimately is harder to work with, because it takes longer for everyone to understand each others' code.
So in short, functional programmers do not "hate" looping construct, but simply don't need them since you can use recursion instead (in practice you actually rarely use recursion directly, but instead use functions like map, fold, sum, etc. which are implemented recursively).
But going further, many loops are actually maps, where you do something independently with each element of a list. Why formulate that as a linear visit to each element?
The usual way to turn them into smaller problems is induction and the easiest way to turn inductive reasoning into code is to write recursive code
(Which I actually use all the time, if only to save a dependency.)
for [0 .. n-1] $ \i ->
...
does pretty much the same thing in Haskell as for i in range(0, n):
...
does in Python.Once these came naturally, doing anything else starts to feel underconstrained and imprecise.
[1] At the very least, on variables outside of itself
We should just be careful when using them just like any other nested structure (if/else/while-do/switch/select/etc.)
Like if I see ``` for ... { for ... { if ... { } else { } // code here ... } ... } ```
That is objectively bad code and not readable.
Nobody hates anything. Most of us just dont like how people misuse them to write bad code because their managers pressure them.