Programming Paradigms

  • imperative programming: modifying mutable variables, using assignments, and control structures such as if-then-else, loops, continue, return, etc inspired by Von Neumann architecture of computers.

  • functional programming programming without mutable variables, assignments, loops, other imperative control structures; programming with functions; functions become values that are produced, consumed, and composed; functions can be passed as parameters and returned as values.

  • logic programming programming in logic; use logical deductions to run a program; programs are a set of logical rules and facts; solutions focus on “what” aspect of the problem and let the system figure out “how” to solve them.

Orthogonal to

  • Object-oriented programming

Functional Programming in Scala

Scala Basics

In [1]:
87 + 145
Out[1]:
res0: Int = 232
In [2]:
val size = 2
Out[2]:
size: Int = 2
In [3]:
5*size
Out[3]:
res2: Int = 10
In [4]:
var pi = 3.14
In [5]:
var radius = 21.5
In [6]:
(2 * pi) * radius
Out[6]:
res5: Double = 135.02
In [7]:
def square(x: Double) =
    x*x
Out[7]:
defined function square
In [8]:
square(2)
Out[8]:
res7: Double = 4.0
In [9]:
square(5+4)
Out[9]:
res8: Double = 81.0
In [10]:
square(square(4))
Out[10]:
res9: Double = 256.0
In [11]:
def sumOfSquares(x: Double, y: Double) = 
    square(x) + square(y)
Out[11]:
defined function sumOfSquares
In [12]:
sumOfSquares(3,4)
Out[12]:
res11: Double = 25.0
In [13]:
def power(x: Double, y: Int): Double = 
    scala.math.pow(x,y)
Out[13]:
defined function power
In [14]:
power(2,3)
Out[14]:
res13: Double = 8.0

Function Evaluation Method (CBV: call by value strategy)

  • Evaluate all function arguments from left to right
  • Replace the function application by the function’s right hand side (body), and at the same time replace the formal parameters of the function by the actual arguments (like the beta-reduction of Lambda Calculus)

e.g.

sumOfSquares(3, 2+2)
sumOfSquares(3, 4)
square(3) + square(4)
3 * 3 + square(4)
9 + square(4)
9 + 4 * 4
9 + 16
25
  • The above strategy is also called the "substitution model"
  • The evaluation reduces the expression to a value
  • The substitution model is formalized in the Lambda Calculus
  • Does every expression evaluate to a value in finite steps? No.
def loop: Int = 
    loop

loop
loop


Another Function Evaluation Method (CBN: call by name strategy)

  • Do not reduce the function arguments to values but delay evaluating until very end
sumOfSquares(3, 2+2)
square(3) + square(2+2)
3*3 + square(2+2)
9 + square(2+2)
9 + (2+2) * (2+2)
9 + 4*(2+2)
9 + 4*4
9+16
25

call-by-name and call-by-value both result in the same value if

  • the reduced expression consists of pure functions and
  • both evaluation strategies terminate

call-by-value evaluates arguments once

call-by-name delays evaluation and can get lucky in not evaluating if argument not needed!

CBV vs CBN

In [15]:
// Consider the function test
def test(x: Int, y: Int) = 
    x * x
Out[15]:
defined function test

test(2,3)

CBV, CBN: test(2,3) = 2*2 = 4

test(3+4,8)

CBV: test(3+4,8) = test(7,8) = 7*7 = 49

CBN: test(3+4,8) = (3+4)*(3+4) = 7*(3+4) = 7*7 = 49

test(7,2*4)

CBV: test(7,2*4) = test(7,8) = 7*7 = 49

CBN: test(7,2*4) = 7*7 = 49

test(3+4,2*8)

Try this on your own…

Scala's Evaluation Strategy

  • Scala normally uses CBV
  • But if type of function parameter is preceded by => Scala uses CBN

Example:

def constOne(x: Int, y: =>Int) = 
    1

constOne(1+2, loop) = constOne(3,loop) = 1

constOne(loop, 1+2) = ...

CBV, CBN, Termination

  • As long as both methods terminate, the end result is the same
  • What if termination is not guaranteed?
  • if CBV terminates then CBN will also terminate not vice versa

Example of CBN terminating but CBV not terminating

def first(x: Int, y: Int) = 
    x
first(1,loop)

under CBN will terminate but will loop forever under CBV

val vs var vs def

val and var use CBV semantics

i.e. expression is evaluated on definition

val defines a fixed value

var defines a variable - which can be modified

val x = (1+2)
val y = 3
var z = 2*y
x = 9 //  error 
z = 12 // ok

def uses CBN semantics

i.e. expression is evaluated when needed

Example

def x = 1 + 2

at this point x is not evaluated to 3

val y = 2 * x

at this point 2*(1+2) is evaluated to make y = 6

Normally we use def for function definitions!

Conditional Expressions

Scala provides an if-else for expressions to be able to choose from two alternatives.

def abs(x: Int) = 
    if (x >= 0) x else -x

Here (x >= 0) is a predicate with type Boolean

Boolean expressions can be composed of

true 
false
!b
b && b
b || b

and of the usual comparison operations

<=, >=, <, >, ==, !=

Rewrite Rules for Booleans/if-then-else expressions

!true —> false

!false —> true

true && e —> e

false && e —> false

true || e —> true

false || e —> e

&&, || use short-circuit evaluation (second operand need not be evaluated)

Rewrite rule for if (b) e1 else e2

if (true) then e1 else e2 —> e1

if (false) then e1 else e2 —> e2

and/or functions using conditional expressions

In [16]:
def and(x: Boolean, y: Boolean): Boolean =
    if (x) y else false

def or(x: Boolean, y: Boolean): Boolean =
  if (x) true else y

and(true,false)
or(false,false)
Out[16]:
defined function and
defined function or
res15_2: Boolean = false
res15_3: Boolean = false

Example Program to compute square root using Newton's Method

To compute sqrt(x):

Start with initial estimate y (let y = 1)

Repeatedly improve the estimate by taking the mean of y and x/y

Let x = 2

Estimate (y) Quotient (x/y) Mean (y+x/y)/2
1 2/1 = 2 1.5
1.5 2/1.5= 1.333 1.4167
1.4167 2/1.4167=1.4118 1.4142
1.4142
In [18]:
// Take 1

def sqrt(x: Double) = 
    sqrtIter(1.0, x)

def sqrtIter(guess: Double, x: Double): Double = 
    if (isGoodEnough(guess, x)) guess else sqrtIter(improve(guess,x), x)

def improve(guess: Double, x: Double) =
    (guess + x/guess)/2

def isGoodEnough(guess: Double, x: Double) =
    scala.math.abs(guess*guess-x) < 0.001

sqrt(2)
Out[18]:
defined function sqrt
defined function sqrtIter
defined function improve
defined function isGoodEnough
res17_4: Double = 1.4142156862745097

Note: Recursive functions in Scala need an explicit return type.

Remark: This version is not very accurate for very very small numbers such 0.001, 0.1e-20 and for very large numbers it may not terminate! e.g. for 1.0e20

Blocks and Lexical Scope

In [19]:
// Take 2

def sqrt(x: Double) = {
    def sqrtIter(guess: Double, x: Double): Double = 
        if (isGoodEnough(guess, x)) guess else sqrtIter(improve(guess,x), x)

    def improve(guess: Double, x: Double) =
        (guess + x/guess)/2

    def isGoodEnough(guess: Double, x: Double) =
        scala.math.abs(guess*guess-x) < 0.001

    sqrtIter(1.0, x)
}

sqrt(2)
Out[19]:
defined function sqrt
res18_1: Double = 1.4142156862745097
  • A block is delimited by braces {…}

  • It contains a sequence of definitions or expressions

  • The last element of a block is an expression that defines the block

  • A block is itself an expression (whose value is the value of the last expression)

In [20]:
val x = 2
def f(y: Int) = y + 1
val result = {
  val x = f(3)
  x * x
} + x
Out[20]:
x: Int = 2
defined function f
result: Int = 18
In [21]:
// Take 3; x visible inside block

def sqrt(x: Double) = {
    def sqrtIter(guess: Double): Double = 
        if (isGoodEnough(guess)) guess else sqrtIter(improve(guess))

    def improve(guess: Double) =
        (guess + x/guess)/2

    def isGoodEnough(guess: Double) =
        scala.math.abs(guess*guess-x) < 0.001

    sqrtIter(1.0)
}

sqrt(2)
Out[21]:
defined function sqrt
res20_1: Double = 1.4142156862745097

Tail Recursion

Recap of Function Application

def f(x1,...,xn) =
      B
f(e1,...,en)
  • evaluate expressions e1,…,en resulting in values v1,…,vn
  • replace f(e1,…,en) by body of function B in which actual arguments replace formal parameters of f; i.e.
f(e1,...,en)

is replaced by B[x1:=v1, ...xn:=vn]

GCD

In [1]:
// famous Euclid Algorithm
def gcd(a: Int, b: Int): Int =
    if (b == 0) a else gcd(b, a%b)

gcd(14,21)
Out[1]:
defined function gcd
res0_1: Int = 7

gcd(14,21)

if (21 == 0) 14 else gcd(21, 14%21)

if (false)14 else gcd(21, 14%21)

gcd(21, 14%21)

gcd(21, 14)

if (14 == 0) 21 else gcd(14, 21%14)

if (false) 21 else gcd(14, 21%14)

gcd(14, 21%14)

gcd(14, 7)

if (7 == 0) 14 else gcd(7, 14%7)

if (false)14 else gcd(7, 14%7)

gcd(7, 14%7)

gcd(7, 0)

if (0 == 0) 7 else gcd(0, 7%0)

7

  • Tail Recursion is better
  • If a function calls itself as its last action, the function’s stack can be reused. This is called tail recursion
  • Tail recursive functions are essentially iterative processes
  • In Scala, tail-recursive functions can be annotated

    @tailrec
    def gcd(a: Int, b: Int): Int = …
  • Factorial

def factorial(n: Int): Int =
    if n <= 0 1 else n* factorial(n-1)
  • Tail recursive version of factorial:
def factorial(n: Int): Int =
     fact(1,n)

def fact(answer: Int, n: Int): Int =
     if (n == 0) answer else fact(n*answer, n - 1)
In [ ]: