Functional programming notes: Side effects

  3 mins read

I am currently learning functional programming, and what better to write a series of posts related to that. Learning functional programming is hard for a object oriented programmer. There are a lot of concepts that you have to understand, to make it easier, I decided to publish my notes as small posts that I will continously publishing as far as I will advancing on this topic. As an student, it could be that some definitions are not accurate enough, I will try to do my best. I will be using scala during this series.

Side effects

Functional programming is based on the simple premise that your functions should not have side effects, they are considered evil in this paradigm. If a function has side effects we call it a procedure, so functions does not have side effects. We consider that a function has a side effect if it modifies a mutable data structure, variable, uses IO, throws an exception or halts an error, all of this things are considered side effects. The reason why side effects are bad is because if you had them, a function can give unpredictable results depending on the state of the system; when a function has no side effects we can execute it anytime, it will return always the same result.

Referential transparency

A function that returns always the same result for the same input we call it a pure function. A pure function therefore is a function with no observable side effects, keep in mind that if we have any side effect on that function the evaluation will give us different results even if we invoke it with the same arguments. We can substitute a function that is pure with its calculated value, for example:

def f(x: Int, y: Int) = x + y

for the input f(2, 2) can be replaced by 4. That is because it does not have any side effects. This ability to replace an expression with its calculated value is it called referential transparency.

Referential transparency is important because it allows us to substitute expressions with values or operations with abstractions. This constraints enables us to think and reason about program evaluation called the substitution model. Hence, we can say that expressions that can be replaced by values are deterministic, as they return an expected value for a given input.

Local side effects

We defined a function with side effects as a function that does internally some mutation, performs some output, etc… But what about functions that always return the same value even tho they do internally some side effect? For example this function:

def sumIntsTo(i: Int) = {
  var result = 0
  0 to i foreach((i) => result = result + i)
  result
}

That function has a side effect, it assigns result for every iteration of the for loop. But even that it does that side effect the function can be replaced by a value because it is deterministic, sumsIntsUntil(5) = 10 and sumsIntsUntil(10) = 45 etc… We say that the function has a local side effect but the user of that function does not care as it does not break our substitution model. Therefore, this function is pure, even if it has a local side effect.

This is a common practice to optimize functions. We can find some examples in the class List of Scala. For instance this is the function drop:

def drop(n: Int): List[A] = {
    var these = this
    var count = n
    while (!these.isEmpty && count > 0) {
      these = these.tail
      count -= 1
    }
    these
  }

As you could see there are a lot of names and terms in this little post, there are a lot of them in this paradigm and they are very important because it gives us a vocabulary to express more complex concepts in terms of other concepts. I will continue explaining more concepts in the next post.

References

Wikipedia
Manning functional programming in Scala

Written by: