├── CHANGELOG.md ├── CONTRIBUTION.md ├── LICENSE ├── README.md ├── TEMPLATE.md └── catalog ├── compose-nested-functions.md ├── extract-function.md ├── name-lambda.md ├── name-value.md ├── rearrange-function-parameters.md ├── recursion-to-filter.md ├── recursion-to-map.md ├── recursion-to-reduce.md ├── reduce-to-map-and-filter.md └── tail-call-optimise-recursion.md /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [Unreleased] 6 | ### Added 7 | - Rearrange Function Parameters 8 | - Name Value 9 | - Extract Function 10 | - Name Lambda 11 | - Compose Nested Functions 12 | 13 | ## [0.0.1] - ????-??-?? 14 | -------------------------------------------------------------------------------- /CONTRIBUTION.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | Please, please, please contribute to this content. I do not want this project 4 | to be my opinion, but rather a catalog of useful refactoring methods identified 5 | by functional programmers. 6 | 7 | ## Discussion 8 | 9 | If you have any questions of constructive thoughts, please bring the 10 | conversation to the GitHub issues section for this project. 11 | 12 | This project is in its early stages, and nothing in here is fixed. All 13 | suggestions, improvements and challenges are welcome. 14 | 15 | ## Amending a Refactoring 16 | 17 | If you find any problems with the description of a refactoring method, please 18 | open a pull request with your suggestions. 19 | 20 | ## Adding a Refactoring 21 | 22 | If you have an idea for a new refactoring method, maybe start with an issue to 23 | discuss it. When you are ready to the actual document, begin the process with a 24 | new pull request using the provided [TEMPLATE.md](TEMPLATE.md). 25 | 26 | ## Renaming a Refactoring 27 | 28 | The existing names are not necessarily the best names; I'd like to engage in a 29 | conversation if you think you have a more suitable name. 30 | 31 | ## Removing a Refactoring 32 | 33 | If you feel a refactoring method is in the catalog but doesn't belong to be, 34 | open and issue and challenge it. 35 | 36 | ## Adding an Example 37 | 38 | All code examples welcome in any language. Open a pull request with your new 39 | example. 40 | 41 | ## Replacing an Example 42 | 43 | If you can think of a better code example than the one provided, please open a 44 | new pull request. 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Tom Oram 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Functional Refactorings 2 | 3 | When writing programs with pure functions, it appears that there are some 4 | refactorings which are more common in this paradigm. Some of these relate to 5 | the [well know catalog of refactorings](http://refactoring.com/catalog/) 6 | introduced by Martin Fowler, but others are not on that list. 7 | 8 | In this catalog, I want to present the ones I have personally identified, 9 | plus the additional ones propose in a session I ran at [SoCraTes 10 | 2016](http://socratesuk.org/) on this topic. 11 | 12 | My hope is that others in the functional community will improve and extend this 13 | catalog to create a useful resource. So please contribute thoughts (issues) 14 | and (improvements) pull requests! 15 | 16 | ## Catalog 17 | 18 | * [Rearrange Function Parameters](catalog/rearrange-function-parameters.md) 19 | * Go Point Free 20 | * Curry Function 21 | * [Recursion to Map](catalog/recursion-to-map.md) 22 | * [Recursion to Filter](catalog/recursion-to-filter.md) 23 | * [Recursion to Reduce](catalog/recursion-to-reduce.md) 24 | * [Name Value](catalog/name-value.md) 25 | * [Extract Function](catalog/extract-function.md) 26 | * [Name Lambda](catalog/name-lambda.md) 27 | * [Compose Nested Functions](catalog/compose-nested-functions.md) 28 | * [Reduce to Map and Filter](catalog/reduce-to-map-and-filter.md) 29 | * Map and Filter to Reduce 30 | * Extract Parameter to Type 31 | * [Tail Call Optimise Recursion](catalog/tail-call-optimise-recursion.md) 32 | * Replace Conditional with Polymorphism 33 | * Chain of Consequences to Maybe (or Either) Monad 34 | 35 | ## Links 36 | 37 | Links for further reading (not yet integrated into this document) 38 | 39 | * http://refactoring.com/catalog/ 40 | * http://victorsavkin.com/post/63551894251/functional-refactoring-in-javascript 41 | * https://www.cs.kent.ac.uk/projects/refactor-fp/publications/Huiqing-thesis.pdf 42 | * https://www.cs.kent.ac.uk/projects/refactor-fp/publications/AFP04.pdf 43 | * https://www.cs.kent.ac.uk/projects/refactor-fp/publications/ChrisThesis.pdf 44 | * http://thepugautomatic.com/2016/01/pattern-matching-complex-strings/ 45 | * https://vimeo.com/45140590 46 | * https://vimeo.com/122645679 47 | * https://speakerdeck.com/lilobase/des-boucles-aux-transducers-pyconfr-2015 48 | -------------------------------------------------------------------------------- /TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # {Refactoring Name} 2 | 3 | ## Relevant To 4 | 5 | *List of existing refactorings which this is variations of or similar to.* 6 | 7 | * {Refactoring Name} 8 | 9 | ## Relates To 10 | 11 | *List of other refactorings in this document which relate to this one in some 12 | way.* 13 | 14 | * {Refactoring Name} 15 | 16 | ## Languages 17 | 18 | *The languages or types of languages it applies to. Be as general as possible.* 19 | 20 | [All|Statically Typed|Dynamically Typed|*list of languages*] 21 | 22 | ## Motivation 23 | 24 | *Description of what problem this refactoring solves and why the problem 25 | exists.* 26 | 27 | ## Description 28 | 29 | *A descripton of what the refactoring involves and how it is performed.* 30 | 31 | ## Examples 32 | 33 | *Examples in various languages.* 34 | 35 | ### {Language Name} 36 | -------------------------------------------------------------------------------- /catalog/compose-nested-functions.md: -------------------------------------------------------------------------------- 1 | # Compose Nested Functions 2 | 3 | ## Relevant To 4 | 5 | * Extract Method 6 | * Composed Method Pattern 7 | * Single Responsibility Principal 8 | 9 | ## Relates To 10 | 11 | * [Compose Nested Functions](compose-nested-functions.md) 12 | 13 | ## Languages 14 | 15 | Languages with higher-order functions 16 | 17 | ## Motivation 18 | 19 | As a function gains complexity, its parts may get extracted out into named 20 | functions creating a deep stack of function calls. In these situations, it 21 | becomes the responsibility of the functions calling inner functions, to push 22 | data down the call stack. 23 | 24 | This has 2 negative properties: 25 | 26 | 1. Function have multiple responsibilities: 27 | * To perform their intended calculation. 28 | * To push data not intended for them down to the next function. 29 | 2. When the call stack gets deep, it is harder to comprehend. 30 | 31 | ## Description 32 | 33 | Remove the unwanted responsibility from the functions, then compose them to 34 | create a pipeline which transforms the data one step at a time. 35 | 36 | ## Examples 37 | 38 | ### Clojure 39 | 40 | #### Before 41 | 42 | ```clojure 43 | (defn tax-multiplier [rate] (+ 1 (/ rate 100))) 44 | (defn with-tax [rate amount] (* amount (tax-multiplier rate))) 45 | 46 | ; item-amounts has the single responsibility of extracting the amount properties 47 | ; from the items. 48 | (defn item-amounts [items] (map :amount items)) 49 | 50 | ; item-amounts-with-tax passes the items down to item-amounts and then adds 51 | ; the tax to exact of the amounts in the result. 52 | (defn item-amounts-with-tax 53 | [tax-rate item] 54 | (map (partial with-tax tax-rate) (item-amounts items))) 55 | 56 | (defn order-total 57 | [tax-rate items] 58 | (sum (item-amounts-with-tax tax-rate items))) 59 | ``` 60 | 61 | #### After 62 | 63 | ```clojure 64 | (defn tax-multiplier [rate] (+ 1 (/ rate 100))) 65 | (defn with-tax [rate amount] (* amount (tax-multiplier rate))) 66 | 67 | ; This function is fine 68 | (defn item-amounts [items] (map :amount items)) 69 | 70 | ; item-amounts-with-tax had two responsibilies. We've now replaced with this 71 | ; function which solely adds tax to a list of amounts 72 | (defn amounts-with-tax 73 | [tax-rate amounts] 74 | (map (partial with-tax tax-rate) amounts)) 75 | 76 | ; Now order-total simply composes all the steps to be applied to the items in 77 | ; in the order - one at a time 78 | (defn order-total 79 | [tax-rate items] 80 | (->> items item-amounts amounts-with-tax sum)) 81 | ``` 82 | -------------------------------------------------------------------------------- /catalog/extract-function.md: -------------------------------------------------------------------------------- 1 | # Extract Function 2 | 3 | ## Relevant To 4 | 5 | * Extract Method 6 | * Single Responsibility Principal 7 | 8 | ## Relates To 9 | 10 | * [Compose Nested Functions](compose-nested-functions.md) 11 | 12 | ## Languages 13 | 14 | All 15 | 16 | ## Motivation 17 | 18 | As a function's expression gains complexity, it is behaviour becomes harder to 19 | comprehend. Extracting a new, well named, function for different parts of that 20 | function can significantly increase clarity. 21 | 22 | Creating smaller functions, with a single responsibility, promotes more 23 | opportunities for code reuse via composition. 24 | 25 | ## Description 26 | 27 | Identify groups in the expression which perform a specific task, then extract 28 | it to a new, well-named function. 29 | 30 | ## Examples 31 | 32 | ### Clojure 33 | 34 | #### Before 35 | ```clojure 36 | (defn order-total 37 | [tax-rate items] 38 | (* (sum (map :amount items)) (+ 1 (/ tax-rate 100)))) 39 | ``` 40 | 41 | #### After 42 | ```clojure 43 | (defn tax-multiplier [rate] (+ 1 (/ rate 100))) 44 | (defn with-tax [rate amount] (* amount (tax-multiplier rate))) 45 | (defn total-amount [items] (sum (map :amount items))) 46 | 47 | (defn order-total 48 | [tax-rate items] 49 | (with-tax tax-rate (total-amount items))) 50 | ``` 51 | -------------------------------------------------------------------------------- /catalog/name-lambda.md: -------------------------------------------------------------------------------- 1 | # Name Lambda 2 | 3 | ## Relevant To 4 | 5 | * Extract Variable 6 | 7 | ## Relates To 8 | 9 | * [Name Value](extract-function.md) 10 | * [Extract Function](extract-function.md) 11 | 12 | ## Languages 13 | 14 | Languages with lambdas 15 | 16 | ## Motivation 17 | 18 | Lambdas/anonymous functions are very useful. However, if is not immediately 19 | clear from it is code what one does, it is helpful to give it a name to 20 | describe it. 21 | 22 | ## Description 23 | 24 | Either store the lambda in a named value or extract it to a new, named 25 | function. 26 | 27 | ## Examples 28 | 29 | ### Clojure 30 | 31 | #### Before 32 | 33 | ```clojure 34 | (defn tax-multiplier [rate] (+ 1 (/ rate 100))) 35 | (defn with-tax [rate amount] (* amount (tax-multiplier rate))) 36 | 37 | (defn order-total 38 | [items tax-rate] 39 | (sum (map (fn [item] (with-tax (:amount item))) items)) 40 | ``` 41 | 42 | #### After 43 | 44 | ```clojure 45 | (defn tax-multiplier [rate] (+ 1 (/ rate 100))) 46 | (defn with-tax [rate amount] (* amount (tax-multiplier rate))) 47 | 48 | (defn item-amount-with-tax [item] (with-tax (:amount item))) 49 | 50 | (defn order-total [tax-rate items] (sum (map item-amount-with-tax items))) 51 | ``` 52 | -------------------------------------------------------------------------------- /catalog/name-value.md: -------------------------------------------------------------------------------- 1 | # Name Value 2 | 3 | ## Relevant To 4 | 5 | * Extract Constant 6 | * Extract Variable 7 | 8 | ## Relates To 9 | 10 | * [Extract Function](extract-function.md) 11 | 12 | ## Languages 13 | 14 | All 15 | 16 | ## Motivation 17 | 18 | As a function's expression gains complexity, it is behaviour becomes harder to 19 | comprehend. Giving names to intermediate steps of the expression can 20 | significantly increase clarity. 21 | 22 | ## Description 23 | 24 | Identify groups in the expression which perform a specific task, then store 25 | them as values with meaningful names. 26 | 27 | ## Examples 28 | 29 | ### Clojure 30 | 31 | #### Before 32 | 33 | ```clojure 34 | (defn order-total 35 | [tax-rate items] 36 | (* (sum (map :amount items)) (+ 1 (/ tax-rate 100)))) 37 | ``` 38 | 39 | #### After 40 | 41 | ```clojure 42 | (defn order-total 43 | [items tax-rate] 44 | (let [tax-multiplier (+ 1 (/ tax-rate 100)) 45 | total-amount (sum (map :amount items)))] 46 | (* tax-multiplier total-amount)) 47 | ``` 48 | -------------------------------------------------------------------------------- /catalog/rearrange-function-parameters.md: -------------------------------------------------------------------------------- 1 | # Rearrange Function Parameters 2 | 3 | ## Relevant To 4 | 5 | * Change Method Signature 6 | 7 | ## Relates To 8 | 9 | * [Go Point Free](go-point-free.md) 10 | 11 | ## Languages 12 | 13 | All 14 | 15 | ## Motivation 16 | 17 | Rearranging function/method parameters is a common thing to do in most 18 | programming languages. There are several reasons to do this, including: 19 | Consistency, style and preference. However, when working with higher order 20 | functions, which is a large aspect of functional programming, currying and 21 | partial application of function arguments becomes a very common technique. To 22 | increase the opportunity to partially apply arguments to functions, it often 23 | makes sense to change the order of the parameters, so that the ones which might 24 | more commonly want to be applied come first. 25 | 26 | Partial application is not the only reason you might consider rearranging you 27 | function parameters. For instance, the pipe mechanism in Elixir and the thread 28 | first macro in Clojure, both inject the value in as the first argument to 29 | functions. Rearranging the order of the parameters the make your API more 30 | pipeable could be a wise refactoring to make. 31 | 32 | ## Description 33 | 34 | Rearranging function parameters is a simple refactoring. When you identify 35 | situations where the order makes it hard to apply arguments to the function as 36 | you would like, you simply change the order. 37 | 38 | ## Examples 39 | 40 | ### Clojure 41 | 42 | #### Given 43 | 44 | ```clojure 45 | (def messages [["hello" "world"] 46 | ["hello" "moon"] 47 | ["goodbye" "world"] 48 | ["goodbye" "moon"]]) 49 | ``` 50 | 51 | #### Before 52 | 53 | ```clojure 54 | (defn list-contains? [coll x] (boolean (some #(= % x) coll))) 55 | 56 | (filter (fn [msg] (list-contains? msg "world")) messages) 57 | ; (["hello" "world"] ["goodbye" "world"]) 58 | ``` 59 | 60 | #### After 61 | 62 | ```clojure 63 | ; Changing the order of x and coll... 64 | (defn list-contains? [x coll] (boolean (some #(= % x) coll))) 65 | 66 | ; ...allows the use of partial application instead of an anonymous function 67 | (filter (partial list-contains? "world") messages) 68 | ; (["hello" "world"] ["goodbye" "world"]) 69 | ``` 70 | 71 | -------------------------------------------------------------------------------- /catalog/recursion-to-filter.md: -------------------------------------------------------------------------------- 1 | # Recursion to Filter 2 | 3 | ## Relevant To 4 | 5 | 6 | * Replace Loop with Collection Closure Method 7 | * Replace Temp with Chain 8 | 9 | ## Relates To 10 | 11 | * Recursion to Map 12 | * Recursion to Reduce 13 | * Tail Call Optimise Recursion 14 | 15 | ## Languages 16 | 17 | All 18 | 19 | ## Motivation 20 | 21 | In functional programming `map`, `reduce` and `filter` (and their synonyms) 22 | become fundamental building blocks for your programs. 23 | 24 | `filter` is used when you want to remove items from a list based on some 25 | predicate test. 26 | 27 | Using `filter` instead of a loop or recursive function creates more declarative 28 | code. It describes the process which you are performing on the data, rather 29 | than how you are implementing it. 30 | 31 | It also separates to the responsibility of iterating over the collection from 32 | the per item filtering rules. This increases reusability and composability. 33 | 34 | ## Description 35 | 36 | This refactoring can be identified when you have a recursive function that does 37 | some form of inline filtering, possibly along with other manipulation. Once 38 | identified, the filtering logic can be extracted out to an anonymous or named 39 | function. 40 | 41 | The filter takes a function (called a predicate) that is passed each element 42 | from the original list. If the function returns `true` then that item is 43 | included in the new list. If it returns false, then that item is not included. 44 | 45 | 46 | ## Examples 47 | 48 | ### Haskell 49 | 50 | #### Given 51 | 52 | ```haskell 53 | numbers = [1, 6, 3, 5, 8, 67, 43, 4, 6, 8] 54 | ``` 55 | 56 | #### Before 57 | 58 | ```haskell 59 | evenNumbersOnly :: Integral a => [a] -> [a] 60 | evenNumbersOnly [] = [] 61 | evenNumbersOnly (x:xs) = if even x 62 | then x : evenNumbersOnly xs 63 | else evenNumbersOnly xs 64 | 65 | evenNumbers = evenNumbersOnly numbers 66 | ``` 67 | 68 | #### After 69 | 70 | ```haskell 71 | evenNumbers = filter even numbers 72 | ``` 73 | -------------------------------------------------------------------------------- /catalog/recursion-to-map.md: -------------------------------------------------------------------------------- 1 | # Recursion to Map 2 | 3 | ## Relates To 4 | 5 | * [Recursion to Filter](recursion-to-filter.md) 6 | * [Recursion to Reduce](recursion-to-reduce.md) 7 | 8 | ## Languages 9 | 10 | All 11 | 12 | ## Motivation 13 | 14 | In functional programming `map`, `reduce` and `filter` (and their synonyms) 15 | become fundamental building blocks for your programs. 16 | 17 | `map` is used when you want create a one-to-one mapping values in a collection 18 | to new values via a transformation function. 19 | 20 | Using `map` instead of a loop or recursive function creates more declarative 21 | code. It describes the process which you are performing on the data, rather 22 | than how you are implementing it. 23 | 24 | It also separates to the responsibility of iterating over the collection from 25 | the per item transformation rules. This increases reusability and 26 | composability. 27 | 28 | ## Description 29 | 30 | Any time where you have a recursive function or a loop which iterates over a 31 | list and peforms a calcuation on each indiviual item, you have a good case to 32 | use `map` instead. 33 | 34 | The in order to refactor you need to extract the transformation function and 35 | then pass that to `map` instead. 36 | 37 | ## Examples 38 | 39 | ### Clojure 40 | 41 | #### Given 42 | 43 | ```clojure 44 | (defn tax-multiplier [rate] (+ 1 (/ rate 100))) 45 | (defn with-tax [rate amount] (* amount (tax-multiplier rate))) 46 | ``` 47 | 48 | #### Before 49 | 50 | ```clojure 51 | (defn amounts-with-tax 52 | [tax-rate amounts] 53 | (if (empty? amounts) 54 | [] 55 | (cons (with-tax (head amounts)) (amounts-with-tax tax-rate (tail amounts))))) 56 | ``` 57 | 58 | #### After 59 | 60 | ```clojure 61 | (defn amounts-with-tax 62 | [tax-rate amounts] 63 | (map (partial with-tax tax-rate) amounts)) 64 | ``` 65 | -------------------------------------------------------------------------------- /catalog/recursion-to-reduce.md: -------------------------------------------------------------------------------- 1 | # Recursion to Reduce 2 | 3 | ## Relates To 4 | 5 | * [Recursion to Filter](recursion-to-filter.md) 6 | * [Recursion to Map](recursion-to-map.md) 7 | 8 | ## Languages 9 | 10 | All 11 | 12 | ## Motivation 13 | 14 | In functional programming `map`, `reduce` and `filter` (and their synonyms) 15 | become fundamental building blocks for your programs. 16 | 17 | `reduce` is used when you want reduce the values in a list down to a single 18 | value via some calculation. 19 | 20 | Using `reduce` instead of a loop or recursive function creates more declarative 21 | code. It describes the process which you are performing on the data, rather 22 | than how you are implementing it. 23 | 24 | It also separates to the responsibility of iterating over the collection from 25 | the calcuation rule. This increases reusability and composability. 26 | 27 | ## Description 28 | 29 | Any time where you have a recursive function or a loop which iterates over a 30 | list and to calculate a single value, you might have a good case to use 31 | `reduce` instead. 32 | 33 | To refactor you need to extract the transformation function and 34 | then pass that to `reduce` instead. 35 | 36 | ## Examples 37 | 38 | ### Clojure 39 | 40 | #### Before 41 | 42 | ```clojure 43 | (defn total-amount 44 | [item-amounts] 45 | (if (empty? item-amounts) 46 | 0 47 | (+ (head item-amounts) (total-amount (tail items))))) 48 | ``` 49 | 50 | #### After 51 | 52 | ```clojure 53 | (defn total-amount [item-amounts] (reduce + 0 item-amounts)) 54 | ``` 55 | 56 | 57 | ### Haskell 58 | 59 | #### Before 60 | 61 | ```haskell 62 | totalAmount :: [Int] -> Int 63 | totalAmount [] = 0 64 | totalAmount (item:items) = item + totalAmount items 65 | ``` 66 | 67 | #### After 68 | 69 | ```haskell 70 | totalAmount :: [Int] -> Int 71 | totalAmount = foldr (+) 0 72 | ``` 73 | -------------------------------------------------------------------------------- /catalog/reduce-to-map-and-filter.md: -------------------------------------------------------------------------------- 1 | # Reduce to Map/Filter/Reduce 2 | 3 | ## Relates To 4 | 5 | * [Recursion to Filter](recursion-to-filter.md) 6 | * [Recursion to Map](recursion-to-map.md) 7 | * [Recursion to Reduce](recursion-to-reduce.md) 8 | 9 | ## Languages 10 | 11 | All 12 | 13 | ## Motivation 14 | 15 | Both `map` and `filter` can be implemented using `reduce`. As a result, complex 16 | reductions are like to contain some `map` and/or `filter` like behaviour. By 17 | extracting these out into separate steps, we can create code which clearly 18 | documents each transformation step as a pipeline of single responsibilities, 19 | rather that creating one complex action. 20 | 21 | ## Description 22 | 23 | Study the body reduction function and identify parts which can are either 24 | translating all values or skipping values. Extract these parts into separate 25 | functions and use the with `map` or `filter` to create a pipeline of 26 | transformations. 27 | 28 | ## Examples 29 | 30 | ### Clojure 31 | 32 | #### Given 33 | 34 | ```clojure 35 | (defn tax-multiplier [rate] (+ 1 (/ rate 100))) 36 | (defn with-tax [rate amount] (* amount (tax-multiplier rate))) 37 | ``` 38 | 39 | #### Before 40 | 41 | ```clojure 42 | (defn order-total 43 | [items] 44 | (reduce 45 | (fn [total item] 46 | (if (:free-gift? item) 47 | total 48 | (+ total (with-tax (:amount item))))) 49 | 0 50 | items)) 51 | ``` 52 | 53 | #### After 54 | 55 | ```clojure 56 | (defn order-total 57 | [items] 58 | (->> items 59 | (filter (compliment :free-gift?)) 60 | (map :amount) 61 | (map with-tax) 62 | (reduce + 0))) 63 | ``` 64 | -------------------------------------------------------------------------------- /catalog/tail-call-optimise-recursion.md: -------------------------------------------------------------------------------- 1 | # Tail Call Optimise Recurssion 2 | 3 | ## Relates To 4 | 5 | * [Recursion to Map](recursion-to-map.md) 6 | * [Recursion to Filter](recursion-to-filter.md) 7 | * [Recursion to Reduce](recursion-to-reduce.md) 8 | 9 | ## Languages 10 | 11 | Languages which support tail call optimisation. 12 | 13 | ## Motivation 14 | 15 | This refactoring is slightly different to all the others. Rather than doing 16 | this for clarity, maintainability or extensibility, this is done to come over a 17 | limitation of the language implementation - i.e. having a fixed stack size. 18 | 19 | Often, the most obvious implementation of a recursive algorithm generates a 20 | Stack Overflow error when the number of iterations exceeds the stack size. With 21 | languages which support tail call optimisation, these recursive functions can 22 | be re-written so that the stack size does not limit them. 23 | 24 | ## Description 25 | 26 | For a function call to not use up an entry on the stack, it must be in the tail 27 | call position. That is, no other operations must happen after it is called in 28 | the calling function. 29 | 30 | To achieve this, an accumulator value which stores all previous calculations 31 | needs to be passed down the stack. 32 | 33 | Performing this refactoring brings you one step closer to using a map or reduce 34 | instead. Therefore, before performing this refactoring you should consider 35 | using them instead. 36 | 37 | ## Examples 38 | 39 | ### Clojure 40 | 41 | #### Before 42 | 43 | ```clojure 44 | (defn factorial [n] 45 | (if (= n 1) 46 | 1 47 | (* n (factorial (dec n)) 48 | 49 | ``` 50 | 51 | When `n` != 1 in this example, the order of execution goes like this: 52 | 1. Get a decremented value of `n` 53 | 2. Call `factorial` with the decemented value of `n` 54 | 3. Multiple the result by `n` 55 | 56 | Therefore, the call to factorial is not in the tail call position because the 57 | multiplication happens afterwards. 58 | 59 | #### After 60 | ```clojure 61 | (defn factorial [n] 62 | (loop [current n 63 | accumulator 1] 64 | (if (= n 1) 65 | accumulator 66 | (recur (dec current) (* current accumulator))))) 67 | 68 | ``` 69 | 70 | Clojure has a [loop](https://clojuredocs.org/clojure.core/loop) macro and 71 | [recur](https://clojuredocs.org/clojure.core/recur) special form which are used 72 | specifically to perform tail call optimised recursions. 73 | --------------------------------------------------------------------------------