Scala 2
Tail Recursion
Evaluating a Function Application
One simple rule: One evaluates a function application f(e1,e2,...,en)
- 对
e1...en进行求值,得到v1...vn - 把等号右边的函数体展开
- 用
v1,...,vn替换函数内的参数e1,...,en
Application Rewriting Rule
This can be formalized as rewriting of the program itself
//定义一个函数f
def f(x1,...,xn) = B; ...f(v1...vn)
//调用了这个函数
f(v1,...,vn)
//将f展开
[v1/x1,...,vn/xn]B
- Here,
[v1/x1,...,vn/xn]意味着:- The expression
Bin which all occurrences ofxihave been replaced byvi [v1/x1,...,vn/xn]is called a substitution
- The expression
Example #1
def gcd(a:Int, b:Int) : Int =
if (b == 0) a else gcd(b,a%b)
gcd(14,21) is evaluated as follows:
-> `if(21 == 0) 14 else gcd (21,14%21) `
-> `if(false) else gcd (21,14%21) `
-> `gcd (21,14%21)`
-> `gcd (21,14)`
-> `if(14) == 0 21 else gcd (14,21%14) `
-> ...
-> `gcd (14,7)`
-> ...
-> `gcd (7,0)`
-> `if (0==0) 7 else gcd(0,7%0)`
-> `7`
Example #2
def factorial(x:Int):Int =
if (x == 0 ) 1 else x*factorial(x-1)
factorial(4) is evaluated as follows:
-> `if (4==0) 1 else 4*factorial(4-1)`
-> ...
-> `4*factorial(3)`
-> `4*3*factorial(2)`
-> `4*3*2factorial(1)`
-> `4*3*2*1*factorial(0)`
-> `4*3*2*1*1`
-> 24
Tail Recursion
Implementation Consideration: if a function calls itself as its last action, the function’s stack frame can be reused. This is called tail recursion
意思是如果一个函数,在最后递归调用自己,则它的stack可以被复用。它的性能变成和使用for循环相同。
例1中,最后一个else会递归,由于它后面没有其它指令了,因此他是Tail Recursive的,例2中,else执行递归后还有一条指令是和x相乘,因此它不满足Tail Recursive
In general, if the last action of a function consists of calling a function(maybe the same, maybe some other functions), one stack frame would be sufficient for both functions. Such calls are called tail-calls
如果一个函数的最后一条指令是调用另一个函数,那么这种调用叫tail-calls,这种情况下一个stack frame可以满足两个函数公用。
但是并不是所有的函数都需要使用尾部递归,JVM允许的递归深度为几百,如果超过这个层次就会stack overflow,因此对于递归层次很深的函数,要使用这种技巧。
针对例2,来实现一种满足Tail Recursive的方法。
object exercise {
def factorial(x:Int):Int =
{
def loop(acc:Int, x:Int):Int =
if ( x== 0 ) acc
else
loop(acc*x,x-1)
loop(1,x)
}
factorial(4)
}
Higher Order Function
- first-class values
- like any other value, a function can be passed as a parameter and returned as a result
- This provides a flexible way to compose programs
- Function takes other functions as parameters or that return functions as results are called HOF
def sum(f: Int=>Int, a:Int, b:Int):Int =
if (a>b) 0
else f(a) + sum(f,a+1,b)
We can then write:
def sumInts(a:Int, b:Int) = sum(id,a,b)
def sumCubes(a:Int, b:Int) = sum(cube,a,b)
def sumFactorials(a:Int,b:Int) = sum(fact,a,b)
where:
def id(x:Int):Int = x
def cube(x:Int):Int = x * x * x
def fact(x:Int):Int = if(x==0) 1 else fact(x-1)
Function Types
The type A => B is the type of a function that takes an argument of type A and returns a result of type B
Anonymous Functions
上面的例子中,将函数最为参数的一个副作用是要创建许多子函数。有些时候定义这些子函数有些冗余,这时候可以使用匿名函数
(x:Int) => x * x * x
Here,(x:Int) => x * x * x是匿名函数
- 参数的类型可以省略,他可以被编译器推断
(x:Int, y:Int) => x+y
所有的匿名函数都可以用def表示:
(x1:T1,...,xn:Tn) => E
//等价于
{
def f(x1:T1,...,xn:Tn) = E;
f
}
因此匿名函数实际上函数的语法糖 = {} + def f
//return a function using block
def sum(f: Int => Int) : (Int,Int)=>Int =
{
def sumF(a:Int, b:Int):Int =
if(a>b) 0
else f(a) + sumF(a+1,b)
sumF
}
使用匿名函数
def sumInt(a:Int, b:Int) = sum(x => x, a, b)
def sumCubes(a:Int,b:Int) = sum(x=>x*x*x,a,b)
Currying
Motivation
Look again at the summation functions:
def sumInts(a:Int, b:Int) = sum(x=>x ,a,b)
- Question
Note that a and b get passed unchanged from sumInts and sumCubes into sum.
Can we be even shorter by getting rid of these parameters?
Functions Returning Functions
Let’s rewrite sum as follows:
def sum(f: Int => Int) : (Int,Int)=>Int =
{
def sumF(a:Int, b:Int):Int =
if(a>b) 0
else f(a) + sumF(a+1,b)
sumF
}
sum is now a function that returns another function.
The returned function sumF applies the given function parameter f and sums the result
def sumIncrease = sum( x=>x+1 )
val result = sumIncrease(2,4)
//也可以直接计算:
val result = sum (x => x+1 )(2,4)
Generally,function application associates to the left. 代码中,函数的运算是左结合:
sum(x=>x+1)(1,10) = (sum(x=>x+1))(1,10)
对于一个返回函数的函数来说,scala提供了一种syntax, 以上面的sum函数为例:
def sum(f:Int=>Int)(a:Int,b:Int) : Int =
if(a>b) 0 else f(a) + sum(f)(a+1,b)
相当于将参数直接代入进来,使sum (x => x+1 )(2,4)这种写法更直观
Expansion of Multiple Parameter Lists
In general, a definition of a function with multiple parameter lists
//带有参数列表的函数
def f(args1)...(argsn) = E
where n>1, 等价于
def f(args1)...(argsn-1) = {def g(argsn)=E; g}
理解:
-
arg1,args2,…,argsn-1实际上都表示匿名函数:
-
def ((f(g)h)z) ... = {def g(x) = E; g} -
其中g(x)是所有这些匿名函数代入后的最终函数
-
whare g is a fresh identifier. Or for short:
def f(args1)...(argsn-1) = (argsn => E)
上面这个式子使用匿名函数表达:
等号左边是匿名函数的求解,等号右边是最终的函数:输入为argsn, 输出为E
也可定义为:
def f = (args1 => (args2 => ... (argsn => E)...))
这种将匿名函数的返回值作为下一个函数的输入,这种风格叫做currying,命名来自Haskell的创使人:Haskell Brooks Curry(1900-1982)
- 分析
sum
def sum(f:Int=>Int)(a:Int,b:Int):Int = E
what is the type of sum?
(Int => Int) => (Int, Int) => Int
- 输入是(Int => Int)
- 输出是(Int,Int)=>Int
- 因此,上面的式子等价于 `(Int => Int) => ((Int, Int) => Int)`
- 但是函数是右结合的,因此左后的括号可以免掉
###Summation with Anonymous Functions
使用匿名函数,我们能简化`sum`函数
```scala
def sumInts(a:Int,b:Int) = sum(x=>x, a,b)
def sumCubes(a:Int,b:Int) = sum(x * x * x, a,b)
###MapReduce
- map,reduce这种函数的基础就是currying
def product(f:Int => Int)(a:Int, b:Int):Int =
if (a > b) 1
else f(a) * product(f)(a+1,b) //> product: (f: Int => Int)(a: Int, b: Int)Int
def fact(n:Int) = product(x => x)(1,n) //> fact: (n: Int)Int
fact(3) //> res1: Int = 6
def mapReduce(map:Int=>Int, combine:(Int,Int)=>Int,zero:Int)(a:Int, b:Int):Int =
if (a>b) zero
else combine(map(a), mapReduce(map,combine,zero)(a+1,b))
//> mapReduce: (map: Int => Int, combine: (Int, Int) => Int, zero: Int)(a: Int,
//| b: Int)Int
mapReduce(x=>x,(x,y)=>x*y,1)(2,4) //> res2: Int = 24
def new_product(f:Int=>Int)(a:Int,b:Int) = mapReduce(f,(x,y)=>x*y,1)(a,b)
//> new_product: (f: Int => Int)(a: Int, b: Int)Int
def new_fact(n:Int) = new_product(x=>x)(1,n)
//> new_fact: (n: Int)Int
new_fact(4) //> res3: Int = 24
##2-3: Finding a fixed point of a function
A number x is called a fixed point of a function f if
f(x) = x
For some functions f we can locate the fixed points by starting with an initial estimate and then by applying f in a repetitive way.
x, f(x), f(f(x)), f(f(f(x))),....
until the values does not vary anymore(or the change is sufficiently small).
This leads to the following function for finding a fixed point:
val tolerance = 0.0001 //> tolerance : Double = 1.0E-4
def isCloseEnough(x:Double,y:Double) = abs((x-y)/x)/x < tolerance
//> isCloseEnough: (x: Double, y: Double)Boolean
def fixedPoint(f:Double => Double)(firstGuess:Double):Double =
{
def iterate(guess:Double):Double = {
val next = f(guess)
if (isCloseEnough(guess,next)) next
else
iterate(next)
}
iterate(firstGuess)
}
###Summary
-
上周我们知道了Function是first-class的
-
这周我们知道HOF可以将function combine起来,可以做入参也可以做返回值
-
As a programmer, one must look for opportunities to abstract and reuse.
-
The highest level of abstraction is not always the best, but it is important to know the techniques, so as to use them properly.
##2-4:Scala syntax summary
We have seen language elements to express types, expressions and definations.
Below, we give their context-free syntax in Extended Backus-Naur form(EBNF), where:
-
|denotes an alternative -
[...]an option(0 or 1) -
{...}a repetition (0 or more)
###Types
| Type = SimpleType | FunctionType |
FunctionType = SimpleType => Type
| `( [Types] )` `=>` Type
SimpleType = Ident
Types = Type {‘,’ Type}
A type can be:
-
A numeric type : Int, Double, Byte, Short, Char, Long, Float
-
Boolean type : true,false
-
String type
-
function type, like
Int => Int,(Int,Int) => Int
###Expressions
An expression can be:
-
An identifier such as
x, isGoodEnough -
An literal, like
0,1.0,"abc" -
An function application, like
sqrt(x) -
An operator application, like
-x,y+x -
An selection , like
math.abs -
An conditional expression, like
if(x<0) -x else x -
A block like
{val x= math.abs(y) ; x+2} -
An anonymous function, like
x => x+1
###Definiations:
Definition:
-
A function definition like
def sum(x:Int,y:Int) = x+y -
A value definitions like
val y = square(2)
Parameter
-
A call-by-value param, like
(x:Int) -
A call-by-name para like
(y: => Double)