DEV Community

Cover image for Loops the functional way
Eckehard
Eckehard

Posted on

Loops the functional way

Immutability is kind of a fashion trend (see here), but for beginners it may be challenging to understand the concepts. How can a program do anything without changing values? I´m not a specialist in FP nor an advocat for this concept, but hopefully I can explain some basics.

Immutable means "unchangeable", and languages like Haskell do indeed know only constants, no variables. If you want to mutate a value, you need to create a copy the new value, which is derived from the old one. The old value never changes. But how do you write loops then?

Let see a classic example in JS:

let i
for (i=0; i<5; i++)
  console.log("the value is "+i)
Enter fullscreen mode Exit fullscreen mode

The same code line is executed multiple time while incrementing the variable i, i is "mutated". What´s wrong about mutation? Honestly, nothing. Loops are a very basic operation of most programming languages and they are easy to implement. In a fictive assembler language loops would look like this:

      set 0 -> ADR_I
LBL1  call console.log(ADR_I)
      INC ADR_I
      COMPARE ADR_I, 5
      JMP_IFNOT --> LBL1
Enter fullscreen mode Exit fullscreen mode

They all work by mutating variables (or adresses). So, how can we build a loop without variables? One recommended way in haskell are recursions. We can use this concept in Javascript too:

function loop(n) {
    if (n > 1) 
        loop(n - 1)
    console.log("the value is "+n)
}
Enter fullscreen mode Exit fullscreen mode

Loop calls itself recursively until the condition is reached. After that, the log starts with the lowest value, so we get

loop(5)
the value is 1
the value is 2
the value is 3
the value is 4
the value is 5
Enter fullscreen mode Exit fullscreen mode

Does this have any advantage for the programmer? If you are a "functional programmer", your are possibly happy to have a solution without mutation. But do you really not mutate anything? Behind the scenes, you use the stack as your memory. This is what happens:

loop(5) -->
  loop(4) -->
    loop(3) -->
      loop(2) -->
        loop(1) -->
        console.log("the value is "+1)
      console.log("the value is "+2)
    console.log("the value is "+3)
  console.log("the value is "+4)
console.log("the value is "+5)
Enter fullscreen mode Exit fullscreen mode

This is ok, if your loop is small, but what, if you need to count to a large number? Very quickly your stack will overflow, as this is limited to a fixed size. Anyway, storing a return address for every loop circle is far from efficient. Surplus to your loop code you need the complete stack handling, which will consume memory and time.

From a theoretical point, FP has some advantages. But in some cases it comes with an overhead that can be fairly expensive. Debugging recursive code by the way is far from convenient. Unlike a standard loop, where you can simply watch the variable value of the iterator, you need to watch the stack pointer. The loop above will return 5 times to the same function, but the 6th time it returns to the caller. Debugging recursive code is often very confusing.

So, like any paradigm, using FP comes not for free. You have to learn not only the language, but also know the concepts and algorithms to use it properly. It is often mentioned, that there is no solution that fits all needs. A paradigm might fit best for a certain class of problems, while it is not ideal for another. You are best suited if you know different paradigms and can choose, what fit´s best.

Happy coding!

Top comments (6)

Collapse
 
artxe2 profile image
Yeom suyun

Extreme functional programming has some significant drawbacks compared to its advantages, but the concept of not modifying the input seems quite useful.
If the guarantee that every function does not modify the input is maintained, in most cases, logic can be handled with shallow copies.

function pure(args) {
   const result = { ...args }
   result.a = something()
   delete result.b
   return result
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
efpage profile image
Eckehard

Most people think, FP and OOP exclude each other, but this is not true. There is no limitation to build parts of your code following the principles of FP and call this functions from within class methods.

Even inside a class it can be most beneficial to limit the number of side effects and use mainly pure functions. This is already good programming style. But using ONLY functions may make things more complilcated then necessary.

Collapse
 
disane profile image
Marco

I guess a good and healthy combination of both styles should be preferred. OOP really benefits FP and vice versa.

Collapse
 
hseritt profile image
Harlin Seritt

Well said.

Collapse
 
artydev profile image
artydev • Edited

Thank you Eckehard ,
Look at this :
Trampoline
TCO

Collapse
 
peerreynders profile image
peerreynders

JavaScriptCore (Safari, Bun) is the only engine that implements TCO

Chrome (V8) has marked TCO as No longer Pursuing.

Tail call optimization in ECMAScript 6