Skip to content

Commit 8c2e374

Browse files
committed
Add TailRecursion section (end of week1)
1 parent 6808020 commit 8c2e374

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,135 @@
11
package scalatutorial.sections
22

3+
import scala.annotation.tailrec
4+
35
/** @param name tail_recursion */
46
object TailRecursion extends ScalaTutorialSection {
57

8+
/**
9+
* = Recursive Function Application =
10+
*
11+
* Let’s compare the evaluation steps of the application of two recursive
12+
* methods.
13+
*
14+
* First, consider `gcd`, a method that computes the greatest common divisor of
15+
* two numbers.
16+
*
17+
* Here's an implementation of `gcd` using Euclid's algorithm.
18+
*
19+
* {{{
20+
* def gcd(a: Int, b: Int): Int =
21+
* if (b == 0) a else gcd(b, a % b)
22+
* }}}
23+
*
24+
* `gcd(14, 21)` is evaluated as follows:
25+
*
26+
* {{{
27+
* gcd(14, 21)
28+
* if (21 == 0) 14 else gcd(21, 14 % 21)
29+
* if (false) 14 else gcd(21, 14 % 21)
30+
* gcd(21, 14 % 21)
31+
* gcd(21, 14)
32+
* if (14 == 0) 21 else gcd(14, 21 % 14)
33+
* gcd(14, 7)
34+
* gcd(7, 14 % 7)
35+
* gcd(7, 0)
36+
* if (0 == 0) 7 else gcd(0, 7 % 0)
37+
* 7
38+
* }}}
39+
*
40+
* Now, consider `factorial`:
41+
*
42+
* {{{
43+
* def factorial(n: Int): Int =
44+
* if (n == 0) 1 else n * factorial(n - 1)
45+
* }}}
46+
*
47+
* `factorial(4)` is evaluated as follows:
48+
*
49+
* {{{
50+
* factorial(4)
51+
* if (4 == 0) 1 else 4 * factorial(4 - 1)
52+
* 4 * factorial(3)
53+
* 4 * (3 * factorial(2))
54+
* 4 * (3 * (2 * factorial(1)))
55+
* 4 * (3 * (2 * (1 * factorial(0)))
56+
* 4 * (3 * (2 * (1 * 1)))
57+
* 24
58+
* }}}
59+
*
60+
* What are the differences between the two sequences?
61+
*
62+
* One important difference is that in the case of `gcd`, we see that
63+
* the reduction sequence essentially oscillates. It goes from one call to
64+
* `gcd` to the next one, and eventually it terminates. In between we have
65+
* expressions that are different from a simple call like if then else's
66+
* but we always come back to this shape of the call of `gcd`. If we look at
67+
* `factorial`, on the other hand we see that in each couple of steps we add
68+
* one more element to our expressions. Our expressions becomes bigger and
69+
* bigger until we end when we finally reduce it to the final value.
70+
*
71+
* = Tail Recursion =
72+
*
73+
* That difference in the rewriting rules actually translates directly to a
74+
* difference in the actual execution on a computer. In fact, it turns out
75+
* that if you have a recursive function that calls itself as its last action,
76+
* then you can reuse the stack frame of that function. This is called ''tail
77+
* recursion''.
78+
*
79+
* And by applying that trick, a tail recursive function can execute in
80+
* constant stack space, so it's really just another formulation of an
81+
* iterative process. We could say a tail recursive function is the functional
82+
* form of a loop, and it executes just as efficiently as a loop.
83+
*
84+
* If we look back at `gcd`, we see that in the else part, `gcd` calls itself
85+
* as its last action. And that translates to a rewriting sequence that was
86+
* essentially constant in size, and that will, in the actual execution on a
87+
* computer, translate into a tail recursive call that can execute in constant
88+
* space.
89+
*
90+
* On the other hand, if you look at `factorial` again, then you'll see that
91+
* after the call to `factorial(n - 1)`, there is still work to be done,
92+
* namely, we had to multiply the result of that call with the number `n`.
93+
* So, that recursive call is not a tail recursive call, and it becomes evident in
94+
* the reduction sequence, where you see that actually there’s a buildup of
95+
* intermediate results that we all have to keep until we can compute the
96+
* final value. So, `factorial` would not be a tail recursive function.
97+
*
98+
* Both `factorial` and `gcd` only call itself but in general, of course, a
99+
* function could call other functions. So the generalization of tail
100+
* recursion is that, if the last action of a function consists of calling
101+
* another function, maybe the same, maybe some other function, the stack
102+
* frame could be reused for both functions. Such calls are called ''tail calls''.
103+
*
104+
* = Tail Recursion in Scala =
105+
*
106+
* In Scala, only directly recursive calls to the current function are optimized.
107+
*
108+
* One can require that a function is tail-recursive using a `@tailrec` annotation:
109+
*
110+
* {{{
111+
* @tailrec
112+
* def gcd(a: Int, b: Int): Int = …
113+
* }}}
114+
*
115+
* If the annotation is given, and the implementation of `gcd` were not tail
116+
* recursive, an error would be issued.
117+
*
118+
* = Exercise =
119+
*
120+
* Complete the following definition of a tail-recursive version of `factorial`:
121+
*/
122+
def tailRecFactorial(res0: Int, res1: Int, res2: Int): Unit = {
123+
def factorial(n: Int): Int = {
124+
@tailrec
125+
def iter(x: Int, result: Int): Int =
126+
if (x == res0) result
127+
else iter(x - res1, result * x)
128+
129+
iter(n, res2)
130+
}
131+
132+
factorial(3) shouldBe 6
133+
factorial(4) shouldBe 24
134+
}
6135
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package scalatutorial.sections
2+
3+
import org.scalacheck.Shapeless._
4+
import org.scalaexercises.Test
5+
import org.scalatest.Spec
6+
import org.scalatest.prop.Checkers
7+
import shapeless.HNil
8+
9+
class LexicalScopesSpec extends Spec with Checkers {
10+
11+
def `check scope rules`: Unit = {
12+
check(Test.testSuccess(LexicalScopes.scopeRules _, 16 :: HNil))
13+
}
14+
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package scalatutorial.sections
2+
3+
import org.scalacheck.Shapeless._
4+
import org.scalaexercises.Test
5+
import org.scalatest.Spec
6+
import org.scalatest.prop.Checkers
7+
import shapeless.HNil
8+
9+
class TailRecursionSpec extends Spec with Checkers {
10+
11+
def `check tail recursive factorial`: Unit = {
12+
check(Test.testSuccess(TailRecursion.tailRecFactorial _, 0 :: 1 :: 1 :: HNil))
13+
}
14+
15+
}

0 commit comments

Comments
 (0)