A procedure is like a function, but instead of returning a value, it causes a side-effect to happen.
Examples of side effects include:
· Updating a variable
· Writing to a file or stream
· Throwing an exception
In languages like Java or C procedures are functions with a void return type. In Scala a procedure has a Unit return type.
For example, deposit and showBalance are procedures:
var balance = 99.0
def deposit(amt: Double): Unit = balance += amt
def showBalance: Unit = println("balance = $" + balance)
Technically, procedures return the only value of type Unit, written ().
() prints out as the empty string.
When we say a function returns a value, we mean that it returns an interesting non-void value.
A pure function returns a value without any side effect.
A hybrid function returns a value and produces a side effect. For example:
def cube(x: Double) =
val result = x * x * x
println(s"$x cubed = $result")
result
Here's a sample call:
scala> cube(3)
3.0 cubed = 27.0
val res4: Double = 27.0
· To be clear, the last line is Scala's repl printing the value, 27, to the console window. The line before is the side effect of the println call.
· This might seem better than a boring function that just cubes numbers, but it makes cube unusable in applications that call cube as a low-level function.
· Avoid hybrids!
A method has an implicit parameter called this.
For example:
class Gladiator(val name: String) {
var
health: Int = 100
def damage(amt: Int) = this.health =
math.max(0, this.health – amt)
def attack(opponent: Gladiator) = {
this.damage(1) // attacking damages
the attacker a little bit
opponent.damage(5)
}
// etc.
}
object Test extends App {
val tania = Gladiator("Tania")
val sonya = Gladiator("Sonya")
tania.attack(sonya)
sonya.attack(tania)
// etc.
}
The attack method has two parameters, the explicit parameter: opponent, and the implicit parameter: this.
When we call:
tania.attack(sonya)
then
this = tania and opponent = sonya.
If we think of methods as object behaviors, then the implicit parameter is telling us who is exhibiting the behavior. Decrementing tania's health might produce a different effect than decrementing sonya's health. It could be the difference between life and death.
A function doesn't have an implicit parameter.
object GladiatorUtils {
def damage(victim: Gladiator, amt: Int)
=
victim.health = math.max(0,
this.health – amt)
def attack(attacker: Gladiator, victim:
Gladiator) =
damage(attacker, 1)
damage(victim, 5)
}
Mathematical functions like sin, exp, and gcd are functions.
We imitate functions in Scala with singleton methods as we did in GladiatorUtils. But to call these functions from outside we must provide the implicit parameter:
GladiatorUtils.attack(sonya, tania) // this = GladiatorUtils
Most of the Scala collections are generic. For example, Array[T] is the type of all arrays of elements of type T, where T can be anything. Array methods simply don't care about T.
Notice the difference from Java where angle braces bracket types:
List<T> // Java
Generic functions/methods are type agnostic. Their algorithms don't depend on the type of their input.
We can define generic functions by making the parameter types itself be a parameter. Here's the syntax:
def id[T](x: T) = x // the identity function (which will be useful later)
Try implementing the identity function in a strongly typed language that doesn't allow generic functions.
For another example, here's a generic method for converting matricies (i.e., 2-dimensional arrays) into strings:
def toString[T](matrix: Array[Array[T]])
=
var result = ""
for(row <- matrix)
for(elem <- row)
result += elem.toString + "
"
result += '\n'
result
Notice the way 2-dimensional arrays are declared. They're simply arrays of arrays of whatever. Our toString function doesn't care.
Here's some test code:
// here's the slick way we create a 3 x 4 2-dimensional array
val mat1 = Array.ofDim[Int](3,
4)
// we can use a fancy for-loop to fill it up
for(i <- 0 until 3; j <- 0 until 4) mat1(i)(j) = i + j
// and now we print it out
println(toString(mat1))
Here's the result:
0 1 2 3
1 2 3 4
2 3 4 5
Scala methods can have default arguments:
def tax(income: Double, rate: Double = .05) =
if (income < 2000) 0 else rate *
income
tax(100000, .1) // = 10000
tax(100000) // = 5000
Scala programmers can overload all of the operators: +, *, !, &&, etc.
class Money(val amt: Double) {
if (amt < 0)
throw
IllegalArgumentException("No negative amounts")
def +(other: Money) = Money(this.amt +
other.amt)
def -(other: Money) = Money(this.amt -
other.amt)
override def toString = amt.toString +
" USD"
// etc.
}
object TestMoney extends App {
val cost1 = Money(32.25)
val cost2 = Money(45.00)
val payment = Money(50)
val debt = cost1 + cost2
- payment
println("debt = " + debt)
// prints "debt = 27.25 USD"
}
This works because:
cost1 + cost2
compiles to
cost1.+(cost2)
Unary methods (i.e., methods with one explicit parameter) can be called using infix notation:
object Test extends App {
val tania = Gladiator("Tania")
val sonya = Gladiator("Sonya")
tania attack
sonya
sonya attack tania
//
etc.
}
Which is better:
tania attack sonya
or what it compiles to:
tania.attack(sonya)
In functional programming functions represent algorithms that can be called and data that can be the inputs and outputs of functions. But the only kind of data in Scala are objects. The trick is that objects can be made to look like functions by giving them an apply method:
object numCalls {
var nums = 0
def apply() = { nums += 1; nums}
}
object TestNumCalls extends App {
println("numCalls() =
" + numCalls())
println("numCalls() =
" + numCalls())
println("numCalls() =
" + numCalls()))
}
Prints:
numCalls() = 1
numCalls() = 2
numCalls() = 3
A variable argument function can be called with any number of inputs. These inputs are collected together into an array that is the actual input:
def sumSquares(nums: Double*): Double =
var result = 0.0
for(num <- nums) result += square(num)
result
println(sumSquares(1, 2, 3))
// 15
println(sumSquares(1, 2, 3, 4, 5)) // 55
println(sumSquares(1, 2, 3, 4, 5, 6, 7, 8, 9)) // 285