I have written the following method to print an arbitrary arity tree structure. (deep first search)
def treeString[A](tree: Tree[A]): String = {
  def DFS(t: Tree[A], acc: String = "", visited: List[String] = Nil, toClose: Int = 0): String = t match {
    case Leaf(id, terminal) if !visited.contains(id) => {
      acc + " " + terminal.name + "[" + id.toString + "] " + ")"*toClose
    }
    case Node(id, op, args @_*) if !visited.contains(id) => {
      var state = acc + " " + op.name + "[" + id.toString + "] " + "("
      var args_visited: List[String] = List(id)
      for (i <- args.tail) {
        state = DFS(i, state , visited ::: args_visited) + " , "
        args_visited = args_visited ++ List(i.id)
      }
      DFS(args.head, state , visited ++ args_visited, toClose + 1)
    }
    case _ => acc
  }
  DFS(tree)
}
The scala compiler claims this function is not tail recursive. However at the tail position i have the proper DFS(..) call. All of the DFS(..) calls made in the loop will be done in an iterative fashion, thus stack safe.
I understand that if the tree is infinitely deep a stack overflow will occur. Do we have a technique to deal with these cases?
                        
I have to agree with @VictorMoroz. The problem is that your statement:
state = DFS(i, state , visited ::: args_visited) + " , "is not in the tail position. It does create a new stack, thus, your method is not tail recursive anymore.
Without deep dive into your data structures, here the way to tail-recursive-traverse a tree in DFS manner.
The key is to use an explicit stack - here in form of a List.
This is correct. Basically, in the most programming languages, each method call reserve some of the stack memory. In simple words tail-recursion allow the compiler to ignore the previous state - do not need the previous stack.
Bare in mind that DFS can not ensure to find the global optima and should not be used if this should be fulfilled.
Postscriptum: The
@tailrecannotation is a friendly hint on the compiler to check in compile time whether your method can be optimized or not (Scala do tail-call optimization by default).