01: Scala Functional Programming basics – pure functions, referential transparency, side effects, etc

Q1. What is a pure function?
A1. A pure function is a function where the following conditions are met:

1) The Input solely determines the output.

2) The function does not change its input.

3) The function does not do anything else except computing the output. This means NO I/O, which is no reading from database, file or input console & no writing to a database, file or output console. No modification of a global variable that is outside the function.

A pure function takes the input & computes the output and returns the output. If a function does anything else outside the scope of computing the output value from the input value like writing the output to a console, read from a file or database, change a global variable, etc then these are known as the side effects. A pure function is a function that is free from these side effects.

Q2. Is below function a pure function?

A2. It is a pure function as it takes the input and computes the output by adding 1 to the input. For the same input, you will always get the same output. It is free from side effects as it does not modify any global variables or don’t do any I/O operations.

Q3. Is below function a pure function?

A3. No as it mutates the global variable “g”. If you run the function addOne(5) first time you get the result as 6, but if you run it again with the same input of 5, you will get the result as 11 because the value of “g” has changed from 1 to 6 after the first run. So, the above function has a side effect, hence NOT a pure function.

Q4. Is there an easy method to validate the purity of a function?
A4. Yes, by testing for the Referential Transparency.

Q. What is a referential transparency?
A. A function is said to be referentially transparent if we can replace it with a corresponding value without changing it’s behaviour. You can do this with pure functions because the same input will always give you the same output.

The example in Q1 is referentially transparent as we can replace addOne(5) with 6.

The example in Q2 is NOT referentially transparent as the addOne(5) value keeps changing, run 1 will be 6, run 2 will be 11, run 3 will be 16 and so on. The behaviour keeps changing.

Q5. Can you give some real world examples where there is no referential transparency?
A5. Here are a few examples where there is NO referential transparency:

1) Code that writes/reads to databases, files or consoles.

2) Functions which depend on time like getDayOfWeek(), getHour(), System.currentTimeMillis etc.

3) Random number generation.

So, can you write real world applications without reading/writing to a database or file? The answer is NO. As a programmer you must strive to write code that follows referential transparency wherever possible.

Q6. Why is the below addOne() function with the println is considered impure even though it returns the same output for a given input?

A6. As per the initial conditions for it be pure there should not be any I/O operation like reading/writing to database, file or console. println is an I/O operation to the console.

Q7. What are the benefits of pure functions?
A7.

1) Easier to reason, debug, test & combine as a pure function has no side effects or hidden I/O, so you can get an idea of what it does just by looking at its signature.

Example 1:

Using map, filter & foldLeft functions.

Example 2:

Using andThen & compose functions.

Note: addAhum _ “is an eta expansion“. It converts methods into functions (i.e. Function1, Function2, etc). This different from addHum(_), which is a partial function meaning x => addHum(x).

So, it is incorrect to do addHum(_).andThen(addAhum(_)) because it evaluates to x => addHum(x).compose(y => addAhum(y)).

The functions andThen() and compose() are defined in Function1, Function2, etc as shown below:

Function1 Scala trait

Function1 Scala trait

2) Easier to parallelize as shown below the input.par returns a parallel collection to map, filter, etc. Scala being a hybrid language supporting both OOP & FP, it supports both var (i.e. mutable) & val (i.e. immutable) variable assignments. When you use “val”, once initialised you cannot modify the variable. How can you create real world apps with immutability?. Scala creates a copy of the objects without modifying them. Immutable objects are thread-safe, hence can be parallelized.

3) Lazy evaluation, where a program may postpone evaluating an expression until just-in-time, when its value is needed. Apache Spark, which is a big data computation engine uses this technique at it’s core.

4) Memoizable, which is an optimisation technique to reduce the computational time at the expense of space via caching the results of computations based on the values of the operands or arguments.

Memoization makes sense only if the result of the function will be the same for a given set of arguments or input. Since pure functions have this property, they’re readily memoizable. Here is an example of cacheing the isOdd(..) function value.

Output:


300+ Java Interview FAQs

800+ Java Interview Q&As

Top