{"categories":["Principles of Programming Languages"],"contentHtml":"<p>Recursion in many languages can cause significant overhead. It might seem that the excess amount of recursion in OCaml would decrease its performance. But it actually doesn't, and that's thanks to <strong>tail recursion</strong>.</p>\n<h2>Implementing Reverse</h2>\n<p>In a functional language with a linked list, it's not a trivial task to reverse a list. There are multiple ways to do it, and they have different performance implications.</p>\n<p>The most basic implementation would just be to concatenate backwards.</p>\n<pre><code>let rec rev l =\n    match l with\n    | [] -&gt; []\n    | x :: xs -&gt; (rev xs) @ [x]\n</code></pre>\n<p>Unfortunately, there's a problem with this. Every time we call <code>rev</code> recursively, we must initialize a new stack frame for every recursive call, leaving the value <code>x</code> in <code>[x]</code> in the calling stack.</p>\n<pre><code>rev [1; 2; 3]\n → (rev [2; 3]) @ [1]\n → ((rev [3]) @ [2]) @ [1]\n → (((rev []) @ [3]) @ [2]) @ [1]\n → (([] @ [3]) @ [2]) @ [1]\n → ([3] @ [2]) @ [1]\n → [3; 2] @ [1]\n → [3; 2; 1]\n</code></pre>\n<p>As you can see, there are a total of three stack frames for a list with three elements. This is pretty bad. However, we can rewrite this function to use tail recursion.</p>\n<pre><code>let rec rev_helper l acc =\n    match l with\n    | [] -&gt; acc\n    | x :: xs -&gt; rev_helper xs (x :: acc)\nlet rev l = rev_helper l []\n</code></pre>\n<p>There doesn't need to be any stack frame here because there are no local variables. The only thing that's being returned is the function call to <code>rev_helper</code>, so the stack can simply change to the <code>rev_helper</code> call without saving the previous stack frame. This tail recursion is incredibly powerful.</p>\n<p>I'll repeat it to be clear. Tail recursion works when the return value of a recursive function is <em>only</em> the recursive function call. The reason that the previous <code>rev</code> function didn't work is because the return value was <code>(rev xs) @ [x]</code>, so <code>x</code> must be saved in a stack frame in order to remember it.</p>\n<p>However, you might have noticed that we had to include a new variable called <code>acc</code>. This accumulator variable is a common pattern for when we want to have tail recursion since the <code>acc</code> is located inside the function call as an argument.</p>\n<p>The power of tail recursion can not be understated here. In a typical function, having excessive stack frames can easily cause a stack overflow for a large data set. With tail recursion, we can have both the lack of side effects associated with recursion and the performance associated with iteration.</p>\n<h2>General Tail Recursion Pattern</h2>\n<pre><code>let f x =\n    let rec aux arg acc =\n        if (* base case *) then acc\n        else\n            let arg' = (* next argument *)\n            let acc' = (* updated accumulator *)\n            aux arg' acc' in\n    aux x (* initial value of accumulator e.g. 0, []*)\n</code></pre>","contentMarkdown":"Recursion in many languages can cause significant overhead. It might seem that the excess amount of recursion in OCaml would decrease its performance. But it actually doesn't, and that's thanks to **tail recursion**.\n\n## Implementing Reverse\n\nIn a functional language with a linked list, it's not a trivial task to reverse a list. There are multiple ways to do it, and they have different performance implications.\n\nThe most basic implementation would just be to concatenate backwards.\n\n```ocaml\nlet rec rev l =\n    match l with\n    | [] -> []\n    | x :: xs -> (rev xs) @ [x]\n```\n\nUnfortunately, there's a problem with this. Every time we call `rev` recursively, we must initialize a new stack frame for every recursive call, leaving the value `x` in `[x]` in the calling stack.\n\n```ocaml\nrev [1; 2; 3]\n → (rev [2; 3]) @ [1]\n → ((rev [3]) @ [2]) @ [1]\n → (((rev []) @ [3]) @ [2]) @ [1]\n → (([] @ [3]) @ [2]) @ [1]\n → ([3] @ [2]) @ [1]\n → [3; 2] @ [1]\n → [3; 2; 1]\n```\n\nAs you can see, there are a total of three stack frames for a list with three elements. This is pretty bad. However, we can rewrite this function to use tail recursion.\n\n```ocaml\nlet rec rev_helper l acc =\n    match l with\n    | [] -> acc\n    | x :: xs -> rev_helper xs (x :: acc)\nlet rev l = rev_helper l []\n```\n\nThere doesn't need to be any stack frame here because there are no local variables. The only thing that's being returned is the function call to `rev_helper`, so the stack can simply change to the `rev_helper` call without saving the previous stack frame. This tail recursion is incredibly powerful.\n\nI'll repeat it to be clear. Tail recursion works when the return value of a recursive function is *only* the recursive function call. The reason that the previous `rev` function didn't work is because the return value was `(rev xs) @ [x]`, so `x` must be saved in a stack frame in order to remember it.\n\nHowever, you might have noticed that we had to include a new variable called `acc`. This accumulator variable is a common pattern for when we want to have tail recursion since the `acc` is located inside the function call as an argument.\n\nThe power of tail recursion can not be understated here. In a typical function, having excessive stack frames can easily cause a stack overflow for a large data set. With tail recursion, we can have both the lack of side effects associated with recursion and the performance associated with iteration.\n\n## General Tail Recursion Pattern\n\n```ocaml\nlet f x =\n    let rec aux arg acc =\n        if (* base case *) then acc\n        else\n            let arg' = (* next argument *)\n            let acc' = (* updated accumulator *)\n            aux arg' acc' in\n    aux x (* initial value of accumulator e.g. 0, []*)\n```","dataUrl":"https://sharifhsn.dev/api/posts/tail-recursion.json","date":"2022-02-15","datePublished":"2022-02-15","description":"Recursion in many languages can cause significant overhead. It might seem that the excess amount of recursion in OCaml would decrease its performance. But it actually doesn't, and …","site":"https://sharifhsn.dev","slug":"tail-recursion","source":"Archive","sourceUrl":null,"tags":["Principles of Programming Languages"],"title":"Tail Recursion in OCaml","url":"https://sharifhsn.dev/blog/tail-recursion/","version":"1","wordCount":497}