Scala Programming Projects
上QQ阅读APP看书,第一时间看更新

Defining pure functions

In the following code,  the pureSquare function is pure:

def pureSquare(x: Int): Int = x * x
val pureExpr = pureSquare(4) + pureSquare(3)
// pureExpr: Int = 25

val pureExpr2 = 16 + 9
// pureExpr2: Int = 25

The functions called pureSquare(4) and pureSquare(3) are referentially transparent—when we replace them with the return value of the function, the program's behavior does not change.

On the other hand, the following function is impure:

var globalState = 1
def impure(x: Int): Int = {
globalState = globalState + x
globalState
}
val impureExpr = impure(3)
// impureExpr: Int = 4
val impureExpr2 = 4

We cannot replace the call to impure(3) with its return value because the return value changes depending on the context. In fact, any function that has side effects is impure. A side effect can be any of the following constructs:

  • Mutating a global variable
  • Printing to the console
  • Opening a network connection
  • Reading/writing data to/from a file
  • Reading/writing data to/from a database
  • More generally, any interaction with the outside world

The following is another example of an impure function:

import scala.util.Random
def impureRand(): Int = Random.nextInt()
impureRand()
//res0: Int = -528134321
val impureExprRand = impureRand() + impureRand()
//impureExprRand: Int = 681209667
val impureExprRand2 = -528134321 + -528134321

You cannot substitute the result of impureRand() with its value because the value changes for every call. If you do so, the program's behavior changes. The call to impureRand() is not referentially transparent, and hence impureRand is impure. In fact, the random() function mutates a global variable to generate a new random number for every call. We can also say that this function is nondeterministic—we cannot predict its return value just by observing its arguments.

We can rewrite our impure function to make it pure, as shown in the following code:

def pureRand(seed: Int): Int = new Random(seed).nextInt()
pureRand(10)
//res1: Int = -1157793070
val pureExprRand = pureRand(10) + pureRand(10)
//pureExprRand: Int = 1979381156
val pureExprRand2 = -1157793070 + -1157793070
//pureExprRand2: Int = 1979381156

You can substitute the call to pureRand(seed) with its value; the function will always return the same value given the same seed. The call to pureRand is referentially transparent, and pureRand is a pure function.

The following is another example of an impure function:

def impurePrint(): Unit = println("Hello impure")
val impureExpr1: Unit = impurePrint()
val impureExpr2: Unit = ()

In the second example, the return value of impurePrint is of the Unit type. There is only one value of this type in the Scala SDK: the ()value. If we replace the call to impurePrint() with (), then the program's behavior changes—in the first case, something will be printed on the console, but not in the second case.