[{"name":"NoUnoptimizedRecursion","comment":"\n\n@docs rule\n\nTail-call optimization makes Elm code more performant and helps prevent stack overflows.\n\nSince this optimization is done silently and under specific circumstances, it is unfortunately relatively easy\nto not notice when the optimization is not being applied. You can find the [reasons why a function would not be optimized below](#fail).\n\nI wrote a whole [article about tail-call optimization](https://jfmengels.net/tail-call-optimization/). Some of the information\nare repeated in this rule's documentation, but it's more complete.\n\n\n## Configuration\n\n@docs Configuration, optOutWithComment, optInWithComment\n\n\n## When (not) to enable this rule\n\nThis rule is useful for both application maintainers and package authors to detect locations where\nperformance could be improved and where stack overflows can happen.\n\nYou should not enable this rule if you currently do not want to invest your time into thinking about performance.\n\n\n## Try it out\n\nYou can try this rule out by running the following command:\n\n```bash\nelm-review --template jfmengels/elm-review-performance/example --rules NoUnoptimizedRecursion\n```\n\nThe rule uses `optOutWithComment \"IGNORE TCO\"` as its configuration.\n\n\n## Success\n\nThis function won't be reported because it is tail-call optimized.\n\n    fun n =\n        if condition n then\n            fun (n - 1)\n\n        else\n            n\n\nThis function won't be reported because it has been tagged as ignored.\n\n    -- With opt-out configuration\n    config =\n        [ NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optOutWithComment \"IGNORE TCO\")\n        ]\n\n    fun n =\n        -- elm-review: IGNORE TCO\n        fun n * n\n\nThis function won't be reported because it has not been tagged.\n\n    -- With opt-in configuration\n    config =\n        [ NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optInWithComment \"ENSURE TCO\")\n        ]\n\n    fun n =\n        fun n * n\n\n\n## Fail\n\nTo understand when a function would not get tail-call optimized, it is important to understand when it would be optimized.\n\nThe Elm compiler is able to apply tail-call optimization **only** when a recursive call **(1)** is a simple function application and **(2)** is the last operation that the function does in a branch.\n\n**(1)** means that while `recurse n = recurse (n - 1)` would be optimized, `recurse n = recurse <| n - 1` would not. Even though you may consider `<|` and `|>` as syntactic sugar for function calls, the compiler doesn't (at least with regard to TCO).\n\nAs for **(2)**, the locations where a recursive call may happen are:\n\n  - branches of an if expression\n  - branches of a case expression\n  - in the body of a let expression\n  - inside simple parentheses\n\nand only if each of the above appeared at the root of the function or in one of the above locations themselves.\n\nThe compiler optimizes every recursive call that adheres to the rules above, and simply doesn't optimize the other\nbranches which would call the function naively and add to the stack frame.\nIt is therefore possible to have **partially tail-call optimized functions**.\n\nFollowing is a list of likely situations that will be reported.\n\n\n### An operation is applied on the result of a function call\n\nThe result of this recursive call gets multiplied by `n`, making the recursive call not the last thing to happen in this branch.\n\n    factorial : Int -> Int\n    factorial n =\n        if n <= 1 then\n            1\n\n        else\n            factorial (n - 1) * n\n\nHint: When you need to apply an operation on the result of a recursive call, what you can do is to add an argument holding the result value and apply the operations on it instead.\n\n    factorialHelp : Int -> Int -> Int\n    factorialHelp n result =\n        if n <= 1 then\n            result\n\n        else\n            factorialHelp (result * n)\n\nand split the function into the one that will do recursive calls (above) and an \"API-facing\" function which will set the initial result value (below).\n\n    factorial : Int -> Int\n    factorial n =\n        factorialHelp n 1\n\n\n### Calls using the |> or <| operators\n\nEven though you may consider these operators as syntactic sugar for function calls, the compiler doesn't and\nthe following won't be optimized. The compiler doesn't special-case these functions and considers them as operators just\nlike `(*)` in the example above.\n\n    fun n =\n        if condition n then\n            fun <| n - 1\n\n        else\n            n\n    fun n =\n        if condition n then\n            (n - 1)\n                |> fun\n\n        else\n            n\n\nThe fix here consists of converting the recursive calls to ones that don't use a pipe operator.\n\n\n### Calls appearing in || or && conditions\n\nThe following won't be optimized.\n\n    isPrefixOf : List a -> List a -> Bool\n    isPrefixOf prefix list =\n        case ( prefix, list ) of\n            ( [], _ ) ->\n                True\n\n            ( _ :: _, [] ) ->\n                False\n\n            ( p :: ps, x :: xs ) ->\n                p == x && isPrefixOf ps xs\n\nThe fix here is consists of using if expressions instead.\n\n    isPrefixOf : List a -> List a -> Bool\n    isPrefixOf prefix list =\n        case ( prefix, list ) of\n            ( [], _ ) ->\n                True\n\n            ( _ :: _, [] ) ->\n                False\n\n            ( p :: ps, x :: xs ) ->\n                if p == x then\n                    isPrefixOf ps xs\n\n                else\n                    False\n\n\n### Calls from let declarations\n\nCalls from let functions won't be optimized.\n\n    fun n =\n        let\n            funHelp y =\n                fun (y - 1)\n        in\n        funHelp n\n\nNote that recursive let functions can be optimized if they call themselves, but calling the parent function\nwill cause the parent to not be optimized.\n\n","unions":[{"name":"Configuration","comment":" Configuration for `NoUnoptimizedRecursion`.\n\nUse [`optOutWithComment`](#optOutWithComment) or [`optInWithComment`](#optInWithComment) to configure this rule.\n\nYou can use comments to tag functions as to be checked or ignored, depending on the configuration option you chose.\nThis comment has to appear on the line after the `=` that follows the declaration of your function. Note that this\ncomment only needs to contain the tag that you're choosing and that it is case-sensitive.\nThe same will apply for functions defined in a let expression, since they can be tail-call optimized as well.\n\n","args":[],"cases":[]}],"aliases":[],"values":[{"name":"optInWithComment","comment":" Reports only the functions tagged with a comment.\n\n    config =\n        [ NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optInWithComment \"ENSURE TCO\")\n        ]\n\nWith the configuration above, the following function would be reported.\n\n    fun n =\n        -- ENSURE TCO\n        if condition n then\n            fun n * n\n\n        else\n            n\n\n","type":"String.String -> NoUnoptimizedRecursion.Configuration"},{"name":"optOutWithComment","comment":" Reports recursive functions by default, opt out functions tagged with a comment.\n\n    config =\n        [ NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optOutWithComment \"IGNORE TCO\")\n        ]\n\nWith the configuration above, the following function would not be reported.\n\n    fun n =\n        -- elm-review: IGNORE TCO\n        if condition n then\n            fun n * n\n\n        else\n            n\n\nThe reasons for allowing to opt-out is because sometimes recursive functions are simply not translatable to\ntail-call optimized ones, for instance the ones that need to recurse over multiple elements (`fun left + fun right`).\n\nI recommend to **not** default to ignoring a reported issue, and instead to discuss with your colleagues how to best\nsolve the error when you encounter it or when you see them ignore an error.\n\nI recommend to use this configuration option as your permanent configuration once you have fixed or opted-out of every function.\n\n","type":"String.String -> NoUnoptimizedRecursion.Configuration"},{"name":"rule","comment":" Reports recursive functions that are not [tail-call optimized](https://functional-programming-in-elm.netlify.app/recursion/tail-call-elimination.html).\n","type":"NoUnoptimizedRecursion.Configuration -> Review.Rule.Rule"}],"binops":[]}]