0

Learning Scala and functional programming in general. In the following tail-recursive factorial implementation:

def factorialTailRec(n: Int) : Int = {

    @tailrec
    def factorialRec(n: Int, f: => Int): Int = {
      if (n == 0) f else factorialRec(n - 1, n * f)
    }

    factorialRec(n, 1)
}

I wonder whether there is any benefit to having the second parameter called by value vs called by name (as I have done). In the first case, every stack frame is burdened with a product. In the second case, if my understanding is correct, the entire chain of products will be carried over to the case if ( n== 0) at the nth stack frame, so we will still have to perform the same number of multiplications. Unfortunately, this is not a product of form a^n, which can be calculated in log_2n steps through repeated squaring, but a product of terms that differ by 1 every time. So I can't see any possible way of optimizing the final product: it will still require the multiplication of O(n) terms.

Is this correct? Is call by value equivalent to call by name here, in terms of complexity?

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
Jason
  • 2,495
  • 4
  • 26
  • 37

2 Answers2

2

Let me just expand a little bit what you've already been told in comments. That's how by-name parameters are desugared by the compiler:

@tailrec
def factorialTailRec(n: Int, f: => Int): Int = {
  if (n == 0) {
    val fEvaluated = f
    fEvaluated
  } else {
    val fEvaluated = f // <-- here we are going deeper into stack. 
    factorialTailRec(n - 1, n * fEvaluated)
  }
}
simpadjo
  • 3,947
  • 1
  • 13
  • 38
  • Ha! Guess what happened when I replaced `val` with `def`. This language is amazing. Thank you. – Jason Jun 04 '19 at 13:13
  • I guess it started working, right? Anyway, I strongly recommend using not more than one "amazing" feature of scala at a time. Usually amazingnesses don't sum up like in your case :) – simpadjo Jun 04 '19 at 14:04
  • Yes, it did. It's just that at that point I remembered that `val` evaluates, whereas `def`... *def*ines a name and waits for the name to be called. I would expect `lazy val` here to have the same effect as `def`. I will investigate once back in front of a PC. You're right about me being over-enthusiastic : premature optimization, root of all evil, etc etc – Jason Jun 04 '19 at 15:38
0

Through experimentation I found out that with the call by name formalism, the method becomes... non-tail recursive! I made this example code to compare factorial tail-recursively, and factorial non-tail-recursively:

 package example

 import scala.annotation.tailrec

 object Factorial extends App {

  val ITERS = 100000

  def factorialTailRec(n: Int) : Int = {
    @tailrec
    def factorialTailRec(n: Int, f: => Int): Int = {
      if (n == 0) f else factorialTailRec(n - 1, n * f)
    }
    factorialTailRec(n, 1)
  }

  for(i <-1 to ITERS) println("factorialTailRec(" + i + ") = " + factorialTailRec(i))


  def factorial(n:Int) : Int = {
    if(n == 0) 1 else n * factorial(n-1)
  }

  for(i <-1 to ITERS) println("factorial(" + i + ") = " + factorial(i))

}

Observe that the inner tailRec function calls the second argument by name. for which the @tailRec annotation still does NOT throw a compile-time error!

I've been playing around with different values for the ITERS variable, and for a value of 100,000, I receive a... StackOverflowError!

Stack overflow error in tail-recursive method

(The result of zero is there because of overflow of Int.)

So I went ahead and changed the signature of factorialTailRec/2, to:

def factorialTailRec(n: Int, f: Int): Int 

i.e call by value for the argument f. This time, the portion of main that runs factorialTailRec finishes absolutely fine, whereas, of course, factorial/1 crashes at the exact same integer.

Very, very interesting. It seems as if call by name in this situation maintains the stack frames because of the need of computation of the products themselves all the way back to the call chain.

Jason
  • 2,495
  • 4
  • 26
  • 37
  • 4
    Your function is tailrec. The thing is that, it is building a non-tailrec function _(in a tailrec way)_ which at the end, it evaluates and the latter breaks the stack. A by-name parameter is like a non args function. – Luis Miguel Mejía Suárez Jun 04 '19 at 02:17
  • I'm afraid I don't quite understand what you mean by "building a non-tailrec function in a tailrec way". My intuition of what's going on here would be that at the point of return (base case of tail recursion) we have to evaluate a product of n terms. If this is the overhead, I would understand. Unfortunately, the terminal output concerns, *strinctly*, a `StackOverflowError`, which makes me think that it can't be tailrec! – Jason Jun 04 '19 at 05:21
  • 3
    @Jason: `factorialTailRec` *is* tail-recursive. However, the *anonymous function* it builds (which is essentially what a by-name parameter is), is not. – Jörg W Mittag Jun 04 '19 at 05:42
  • @Jason you are correct that at the end, the program evaluates the product of n terms. The thing is, this product is built as a non-tail recursive anonymous function, as Jörg W Mittag said. When you use by-value parameters, it should work, because you won't be building a function, but rather a value. – Luis Miguel Mejía Suárez Jun 04 '19 at 11:50
  • Got it, everyone. Thank you very much. This generalizes, right? There is no way to have a tail-recursive function if any one of its arguments are called by name. – Jason Jun 04 '19 at 13:08