Scala 5 (More about Functions)

Download Report

Transcript Scala 5 (More about Functions)

More about functions
Plus a few random things
Tail recursion

A function is said to be tail recursive if the recursive call is the very last
thing it does, in any recursive branch

Example #1:
def collatz(n: Int): Int = {
if (n == 1) 1
else if (n % 2 == 0) collatz(n / 2)
else collatz(3 * n + 1)
}



There is no recursion if n == 1
In each other case, the last operation is a recursive call
A good compiler can replace tail recursion with a loop


def collatz(n: Int): Int = {
var nn = n
while (nn != 1) {
if (nn % 2 == 0) nn = nn / 2
else nn = 3 * nn + 1
}
nn
}
This is desirable because loops don’t cause the stack to grow
2
Factorial

Here’s everybody’s first recursive function:
def factorial(n: Long): Long = {
if (n == 0 || n == 1) 1
else n * factorial(n - 1)
}


This is not tail recursive, because there is a multiplication that is
performed after the recursive call
Can the factorial function be rewritten to be tail recursive?



Yes it can, and the technique for doing so can be applied to a lot of
other functions, so it’s worth learning
I’ll leave this as an exercise, since if you figure it out yourself you are
much more likely to remember how to do it
Here are three hints, which you can uncover one at at time if you get
stuck (the text is white; select it and change the color)
1. Use two functions; the second one should do all the recursive work
2. The second function should have two parameters
3. One of the parameters should be 1
3
Currying



Here’s a function to find the hypotenuse of a right triangle:
def hyp(a: Double, b: Double) = sqrt(a * a + b * b)
Suppose you were to call it this way:
val h = hyp(5.0, 12.0)
You can think of this as the compiler substituting a value for each
variable



Now suppose we stop after step 1


Suppose we substitute 5.0 for a first:
sqrt(a * a + b * b) becomes sqrt(5.0 * 5.0 + b * b)
And afterwards substitute 12.0 for b:
sqrt(5.0 * 5.0 + b * b) becomes sqrt(5.0 * 5.0 + 12.0 * 12.0)
which is then calculated (giving 13.0)
What we have after step 1 is a partially applied function
Currying is a way to make partially applied functions
4
Currying in Scala

Instead of giving our hyp function one parameter list, we can give it two
parameter lists:


Now when we call it, we have to give it two argument lists, not just one list
with two arguments:


scala> hyp(5)(12)
res2: Double = 13.0
We can stop with just the first list (but we need to use an underscore to tell
Scala we know we left something out)



scala> def hyp(a: Double)(b: Double) = sqrt(a * a + b * b)
hyp: (a: Double)(b: Double)Double
scala> val h5 = hyp(5) _
h5: (Double) => Double = <function1>
scala> h5(12)
res3: Double = 13.0
With more parameters, we can break up the parameter list any way we like

def foo(a: Int)(b: Int, c: String)(d: Double) = {...}
5
Another way to curry

Here’s a function written in the usual way:


Here we’re calling it in the usual way:


scala> avg(5, 12)
res6: Double = 8.5
Here we’re calling it with the first argument specified (5) but the
second argument unspecified (_)




scala> def avg(a: Double, b: Double) = (a + b) /2.0
avg: (a: Double,b: Double)Double
scala> val a5 = avg(5, _: Double)
a5: (Double) => Double = <function1>
Notice that we had to specify the type of the omitted parameter
Also pay careful attention to the type of the result
Now let’s call our new, curried function:

scala> a5(12)
res7: Double = 8.5
6
Call-by-name parameters

When using a function as a argument, the function parameter is used only
when requested:


Other types of parameters are first evaluated, then their values are passed into
the function:



Example:
def foo(f: Int => Int) { println("In foo with " + f(10)) }
foo(factorial) // prints "In foo with 3628800"
Example:
def foo(n: Long) { println(n} }
foo(factorial(10))
What is passed into the function is the number 3628800
By using => as the first part of the type in a function definition, the function
will treat a parameter as a function


Example:
def foo(f: => Int) { println("In foo with " + f(10)) }
foo(factorial) // prints "In foo with 3628800"
This is an example of call-by-name
7
Lazy evaluation

Lazy evaluation is a technique whereby a computation is not actually
performed until and unless the result is needed


Ranges in Scala are lazy


lazy val xxx = someLongAndExpensiveComputation
...
if (we decide that we need xxx) {
use xxx // The computation is done here
}
scala> println((1 to Int.MaxValue) take 3)
Range(1, 2, 3)
The Stream class supports lazy evaluation, and therefore allows “infinite” data
structures (example from Programming Scala by Wampler & Payne)



scala> lazy val fib: Stream[Int] = Stream.cons(0, Stream.cons(1,
fib.zip(fib.tail).map(p => p._1 + p._2)))
fib: Stream[Int] = <lazy>
cons is like ::
scala> fib.take(10).print
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, empty
8
Building control structures

When calling a curried function, you can replace the last set of parentheses
with braces


scala> hyp(5) { 12 }
res27: Double = 13.0
You can only put expressions inside parentheses, but you can put multiple
“statements” inside braces


This, plus currying, plus lazy evaluation, allows you to build your own control
structures
scala> def repeat(n: Int)(body: => Unit) {
| for (i <- 1 to n) body
|}
repeat: (n: Int)(body: => Unit)Unit
scala> repeat(3) {
| println("Hello")
|}
Hello
Hello
Hello
9
XML

XML is built into Scala as a data type—not just as
something supported by some package


scala> val name = "Dave"
name: java.lang.String = Dave
scala> val x = <html><body><h1>Hello, {name}!</h1></body></html>
x: scala.xml.Elem = <html><body><h1>Hello, Dave!</h1></body></html>
10
DSL

Scala has superb support for Domain Specific Languages



A parser combinator is a function that takes only other functions as parameters and
returns only functions
BNF is essentially built into Scala as a combinator library
Example: Arithmetic expressions (adapted from Odersky, pp. 620-621)


BNF:
<expr> ::= <term> { “+” <term> | “-” <term> }
<term> ::= <factor> { “*” <factor> | “/” <factor> }
<factor> ::= <floatingPointNumber> | “(” <expr> “)”
Scala (~ is used to indicate one thing follows another in sequence):
import scala.util.parsing.combinator._
class Arith extends JavaTokenParsers {
def expr: Parser[Any] = term ~ rep("+" ~ term | "-" ~ term)
def term: Parser[Any] = factor ~ rep("*" ~ factor | "/" ~ factor)
def factor: Parser[Any] = floatingPointNumber | "(" ~ expr ~ ")"
}
11
The End
12