More Scala Functions
Download
Report
Transcript More Scala 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