Pure functions

One of the most important principles of functional programming is pure functions. So what are pure functions and why do we care about them? In this section, we will address this important feature of functional programming. One of the best practices of functional programming is to implement your programs such that the core of your program/application is made from pure functions and all the I/O functions or side effects such as network overhead and exceptions are in an exposed external layer.

So what are the benefits of pure functions? Pure functions are normally smaller than normal functions (although it depends on other factors such as programming language) and even easier to interpret and understand for the human brain because it looks like a mathematical function.

Yet, you might argue against this since most developers still find imperative programming more understandable! Pure functions are much easier to implement and test. Let's demonstrate this by an example. Suppose we have the following two separate functions:

def pureFunc(cityName: String) = s"I live in $cityName"
def notpureFunc(cityName: String) = println(s"I live in $cityName")

So in the previous two examples, if you want to test the pureFunc pure function, we just assert the return value that's coming from the pure function with what we are expecting based on our input such as:

assert(pureFunc("Dublin") == "I live in Dublin")

But on the other side, if we wanted to test our notpureFunc impure function then we need to redirect the standard output and then apply assertion on it. The next practical tip is that functional programming makes programmers more productive because, as mentioned earlier, pure functions are smaller and easier to write and you can easily compose them together. Also, the duplication of code is minimal and you can easily reuse your code. Now let's demonstrate this advantage with a better example. Consider these two functions:

scala> def pureMul(x: Int, y: Int) = x * y
pureMul: (x: Int, y: Int)Int

scala> def notpureMul(x: Int, y: Int) = println(x * y)
notpureMul: (x: Int, y: Int)Unit

However, there might be side effects of mutability; using a pure function (that is, without mutability) helps us reason about and test code:

def pureIncrease(x: Int) = x + 1

This one is advantageous and very easy to interpret and use. However, let's see another example:

varinc = 0
def impureIncrease() = {
inc += 1
inc
}

Now, consider how confusing this could be: what will be the output in a multithreaded environment? As you can see, we can easily use our pure function, pureMul, to multiply any sequence of numbers, unlike our notpureMul impure function. Let's demonstrate this by the following example:

scala> Seq.range(1,10).reduce(pureMul)
res0: Int = 362880

The complete code for the preceding examples can be shown as follows (methods were called using some real values):

package com.chapter3.ScalaFP

object PureAndNonPureFunction {
def pureFunc(cityName: String) = s"I live in $cityName"
def notpureFunc(cityName: String) = println(s"I live in $cityName")
def pureMul(x: Int, y: Int) = x * y
def notpureMul(x: Int, y: Int) = println(x * y)

def main(args: Array[String]) {
//Now call all the methods with some real values
pureFunc("Galway") //Does not print anything
notpureFunc("Dublin") //Prints I live in Dublin
pureMul(10, 25) //Again does not print anything
notpureMul(10, 25) // Prints the multiplicaiton -i.e. 250

//Now call pureMul method in a different way
val data = Seq.range(1,10).reduce(pureMul)
println(s"My sequence is: " + data)
}
}

The output of the preceding code is as follows:

I live in Dublin 250 
My sequence is: 362880

As discussed earlier, you can consider pure functions as one of the most important features of functional programming and as a best practice; you need to build the core of your application using pure functions.

Functions versus methods:
In the programming realm, a function is a piece of code called by a name. Data (as an argument or as a parameter) can be passed to operate on and can return data (optionally). All data passed to a function is passed explicitly. A method, on the other hand, is also a piece of code that is called by a name too. However, a method is always associated with an object. Sounds similar? Well! In most cases, a method is identical to a function except for two key differences:
1. A method is implicitly passed the object on which it was called.
2. A method is able to operate on data that is contained within the class.

It is already stated in the previous chapter that an object is an instance of a class--the class is the definition, the object is an instance of that data.

Now it's time to learn about higher-order functions. However, before that, we should learn one more important concept in functional Scala--anonymous functions. Through this, we will also learn how to use the lambda expression with functional Scala.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.198.132