In [1]:
// Test scala env.
println("Hello, Scala!")
Construct programs using functions that have no side effects.
A function with input type A and output type B (written in Scala as a single type: A => B ) is a computation which relates every value a of type A to exactly one value b of type B such that b is determined solely by the value of a .
For example, a function intToString having type Int => String will take every integer to a corresponding string. Furthermore, if it really is a function, it will do nothing else.
In other words, a function has no observable effect on the execution of the program other than to compute a result given its inputs; we say that it has no side effects.
We can formalize this idea of pure functions by using the concept of referential transparency (RT): in any program, the expression can be replaced by its result without changing the meaning of the program.
Here is an example of RT and non-RT:
In [2]:
val x = "Hello, Scala!"
val r1 = x.reverse
val r2 = x.reverse
Out[2]:
In [4]:
// Now replace all the occurrences of the term x with the expression
// referenced by x (its definition), as follows:
val r1 = "Hello, Scala!".reverse
var r2 = "Hello, Scala!".reverse
// The values of r1 and r2 are the same as before, so x was referentially transparent.
// NOTE: x is a function!
Out[4]:
In [5]:
// Non-RT case
var x = new StringBuilder("Hello")
var y = x.append(", Scala!")
var r1 = y.toString
var r2 = y.toString
Out[5]:
In [6]:
// Let's now see how this side effect breaks RT.
// Replace all y with expression instead.
val x = new StringBuilder("Hello")
val r1 = x.append(", World").toString
var r2 = x.append(", World").toString
Out[6]:
FP buys us greater modularity! Why?
A pure function is modular and composable because it separates the logic of the computation itself from "what to do with the result" and "how to obtain the input"; it is a black box.
Functional programmers often speak of implementing programs with a pure core and a thin layer on the outside that handles effects. We will return to this principle again and again throughout the book.x
Here is an example:
In [7]:
case class Player(name: String, score: Int) // data type Player
def printWinner(p: Player): Unit =
println(p.name + " is the winner!")
def declareWinner(p1: Player, p2: Player): Unit = // takes twp Players
if (p1.score > p2.score) printWinner(p1)
else printWinner(p2)
Out[7]:
In [8]:
val sue = Player("Sue", 7)
val bob = Player("Bob", 8)
declareWinner(sue, bob)
Out[8]:
In [10]:
/*
* In above case, the logic part (compare the score) and the print part
* are in the same function declareWinner.
*
*
* How to reuse the function? How to seperate the logic and print?
*/
// a pure function: takes two Player, return a Player
// it can be re-use easily!
def winner(p1: Player, p2: Player): Player =
if (p1.score > p2.score) p1 else p2
// print the winner
def declareWinner(p1: Player, p2: Player): Unit =
printWinner(winner(p1, p2))
// List of Players
val players = List(Player("Sue", 7),
Player("Bob", 8),
Player("Joe", 9))
// reduce the list to just the winner
val p = players.reduceLeft(winner)
// print the winner
printWinner(p)
/* What we learn:
* any function with side effects can be split into a pure function at
* the "core" and possibly a pair of functions with side effects; one
* on the input side, and one on the output side.
* /
Out[10]:
In [ ]: