I'm currently learning about functional programming using Scala.
I'm also learning about loops and how they should be avoided due to side-effects.
What does this mean?
I'm currently learning about functional programming using Scala.
I'm also learning about loops and how they should be avoided due to side-effects.
What does this mean?
Functions in Purely Functional Programming Languages are exactly like Functions in Mathematics: they produce a result value based on their argument values and only based on their argument values.
A Side-Effect (often just called Effect) is everything else. I.e. everything that is not reading the arguments and returning a result is a Side-Effect.
This includes, but is not limited to:
That last one is really important: calling an impure function makes a function impure. Side-Effects are infectious in this sense.
Note that saying "you are only allowed to read the arguments" is somewhat simplified. In general, we consider the environment of the function also to be kind of an "invisible" argument. That means, for example, a Closure is allowed to read variables from the environment it closes over. A function is allowed to read global variables.
Scala is an Object-Oriented Language and has Methods which have an invisible this
argument, which they are allowed to read.
The important property here is called Referential Transparency. A Function or an Expression is Referentially Transparent if you can replace it with its value without changing the meaning of the program (and vice versa).
Note that generally, the terms "Pure" or "Purely Functional", "Referentially Transparent", and "Side-Effect Free" are used interchangeably.
For example, in this following program, the (sub-)expression 2 + 3
is Referentially Transparent because I can replace it with its value 5
without changing the meaning of the program:
println(2 + 3)
has exactly the same meaning as
println(5)
However, the println
Method is not Referentially Transparent, because if I replace it with its value, the meaning of the program changes:
println(2 + 3)
does not have the same meaning as
()
Which is simply the value ()
(pronounced "unit"), which is the return value of println
.
A consequence of this is that a Referentially Transparent Function always returns the same result value when passed the same arguments. For all code, you should get the same output for the same input. Or more generally, if you do the same thing over and over, the same result should happen over and over.
And that's where the connection between loops and Side-Effects lies: a loop is doing the same thing over and over. So, it should have the same result over and over. But it doesn't: it will have a different result at least once, namely it will finish. (Unless it is an infinite loop.)
In order for loops to make sense, they must have Side-Effects. However, a Purely Functional program must not have Side-Effects. Therefore, loops cannot possibly make sense in a Purely Functional program.
As an additional example from @Jörg, take this naive loop using imperative language written in Scala:
def printUpTo(limit: Int): Unit = {
var i = 0
while(i <= limit)
{
println("i = " + i)
i += 1
// in another part of the loop
if (i % 5 == 0) { i += 1 } // ops. We should not evaluate "i" here.
}
}
Inside this loop there is a variable declared as var i
which is a state that is changed on each iteration. While this state change isn't visible from the outside (a new copy of the variable is created each time the function is entered), var often means there's unnecessary clutter in the code and that it can be simplified. And indeed it can.
As a functional programmer we must strive to use immutable states everywhere. In this loop example if someone changes the value of var i
in another place, like in if (i % 5 == 0) { i += 1 }
by lack of attention, it will be hard to debug and find. This is a side effect that we must avoid. So, using immutable states avoids these kind of bugs. Here is the same example using imutable state.
def printUpToFunc1(limit: Int): Unit = {
for(i <- (0 to limit)) {
println("i = " + i)
}
}
And we can make the code more clear using only foreach
:
def printUpToFunc2(limit: Int): Unit = {
(0 to limit).foreach {
i => println("i = " + i)
}
}
And smaller...
def printUpToFunc3(limit: Int): Unit = (0 to limit).foreach(println)
All those are good answers. Just add one quick point if you come from another language.
Void Function
A function returns nothing, such as void
, implies there is a side effect.
For example, if you have this code in c#
void Log (string message) => Logger.WriteLine(message);
This causes side effect, which writes something to a logger.
Does it matter? Probably you don't care. However, what about this?
def SubmitOrder(order: Order): Unit =
{
// code that submits an order
}
This will not be good. See later.
Why side effect is bad?
Other than some obvious reasons, including:
The most important thing is, it is annoying to test.
How to avoid side effect?
An easy way is always try to return something. (Of course, still try not to change the state internally, closure is fine).
For example, the previous example, if instead of Unit
, we have:
def SubmitOrder(order: Order): Either[SubmittedOrder, OrderSubmissionError] =
{
// code that submits an order
}
this will be much better, it tells the reader there is a side effect and what might happen.
Side Effect in Loop
Now go back to your question about loop, without analyzing your real case, it is hard to suggest how to avoid side effect from a loop.
However, if you are writing a function and then you want to write a loop that calls the function, make sure that function does not modify a local variable or a state in somewhere else.
© 2022 - 2024 — McMap. All rights reserved.