官术网_书友最值得收藏!

  • Scala Programming Projects
  • Mikael Valot Nicolas Jorand
  • 423字
  • 2021-07-23 16:25:21

Understanding tail-recursion

Add the following tests underneath the previous one:

"RetCalc.nbOfMonthsSaving" should {
"calculate how long I need to save before I can retire" in {...}

"not crash if the resulting nbOfMonths is very high" in {
val actual = RetCalc.nbOfMonthsSaving(
interestRate = 0.01 / 12, nbOfMonthsInRetirement = 40 * 12,
netIncome = 3000, currentExpenses = 2999, initialCapital = 0)
val expected = 8280
actual should ===(expected)
}

"not loop forever if I enter bad parameters" in pending

We will implement the not loop forever later on. It is a good practice to write pending tests as soon as you think about new edge cases or other use cases for your function. It helps to keep a direction toward the end goal, and gives some momentum—as soon as you have made the previous test pass, you know exactly what test you need to write next.

Run the test, and it will fail with StackOverflowError. This is because each time loop is called recursively, local variables are saved onto the JVM stack. The size of the stack is quite small, and as we can see, it is quite easy to fill it up. Fortunately, there is a mechanism in the Scala compiler to automatically transform tail-recursive calls into a while loop. We say that a recursive call (the call to the loop function inside the loop function) is tail-recursive when it is the last instruction of the function.

We can easily change our previous code to make it tail-recursive. Select returnValue, and hit cmd + O to inline the variable. The body of the loop function should look like this:

@tailrec
def
loop(months: Int): Int = {
val (capitalAtRetirement, capitalAfterDeath) = simulatePlan(
interestRate = interestRate,
nbOfMonthsSaving = months, nbOfMonthsInRetirement =
nbOfMonthsInRetirement,
netIncome = netIncome, currentExpenses = currentExpenses,
initialCapital = initialCapital)

if (capitalAfterDeath > 0.0)
months
else
loop(months + 1)

IntelliJ changed the small symbol in the editor's margin to highlight the fact that our function is now tail-recursive. Run the test again and it should pass. It is a good practice to also put an annotation @tailrec before the function definition. This tells the compiler that it must verify that the function is indeed tail-recursive. If you annotate a @tailrec function that is not tail-recursive, the compiler will raise an error.

When you know that the depth of a recursive call can be high (more than 100), always make sure it is tail-recursive.
When you write a tail-recursive function, always annotate it with @tailrec to let the compiler verify it.
主站蜘蛛池模板: 徐闻县| 平潭县| 义乌市| 广水市| 弥勒县| 合作市| 石河子市| 开封县| 巴楚县| 合肥市| 蕉岭县| 辉县市| 广德县| 汪清县| 额尔古纳市| 正阳县| 阿尔山市| 临城县| 通州区| 馆陶县| 沅陵县| 沾化县| 中江县| 嘉义市| 石家庄市| 双流县| 黔西县| 乌苏市| 河北省| 连山| 兴和县| 浏阳市| 阿合奇县| 霍城县| 茶陵县| 启东市| 清苑县| 镇坪县| 息烽县| 新平| 开鲁县|