|
| 1 | +--- |
| 2 | +title: Currying |
| 3 | +category: Functional |
| 4 | +language: en |
| 5 | +tag: |
| 6 | +- Decoupling |
| 7 | +--- |
| 8 | + |
| 9 | +## Name / classification |
| 10 | +Currying |
| 11 | + |
| 12 | +## Intent |
| 13 | +Currying decomposes a function that takes multiple arguments into a sequence of functions that each take a single argument. |
| 14 | +Curried functions are useful since they can be used to create new functions with lower arity to perform more specialised tasks |
| 15 | +in a concise and readable manner. This is done via partial application. |
| 16 | + |
| 17 | +## Explanation |
| 18 | +Real-world example |
| 19 | +> Consider a librarian who wants to populate their library with books. The librarian wants functions which can create |
| 20 | +> books corresponding to specific genres and authors. Currying makes this possible by writing a curried book builder |
| 21 | +> function and utilising partial application. |
| 22 | +
|
| 23 | +In plain words |
| 24 | +> Decompose a function that take multiple arguments into multiple functions that take a single argument. |
| 25 | +
|
| 26 | +Wikipedia says |
| 27 | +> Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that |
| 28 | +> each take a single argument. Given a function $f:(X \times Y) \rightarrow Z$, currying constructs a new function |
| 29 | +> $h:X \rightarrow (Y\rightarrow Z)$. $h$ takes an argument from $X$ and returns a function which maps $Y$ to $Z$. Hence, |
| 30 | +> $h(x)(y) = f(x, y)$. |
| 31 | +
|
| 32 | +Programmatic example |
| 33 | +We have a `Book` class and `Genre` enum. |
| 34 | +```java |
| 35 | +public class Book { |
| 36 | + private final Genre genre; |
| 37 | + private final String author; |
| 38 | + private final String title; |
| 39 | + private final LocalDate publicationDate; |
| 40 | + |
| 41 | + Book(Genre genre, String author, String title, LocalDate publicationDate) { |
| 42 | + this.genre = genre; |
| 43 | + this.author = author; |
| 44 | + this.title = title; |
| 45 | + this.publicationDate = publicationDate; |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +public enum Genre { |
| 50 | + FANTASY, |
| 51 | + HORROR, |
| 52 | + SCI_FI; |
| 53 | +} |
| 54 | +``` |
| 55 | +We could easily create a `Book` object with the following method: |
| 56 | +```java |
| 57 | +Book createBook(Genre genre, String author, String title, LocalDate publicationDate) { |
| 58 | + return new Book(genre, author, title, publicationDate); |
| 59 | +} |
| 60 | +``` |
| 61 | +However, what if we only wanted to create books from the `FANTASY` genre? We could pass in the `FANTASY` parameter on each method call; however, this is repetitive. We could define a new method specifically for creating `FANTASY` books; however, it is infeasible to create a new method for each book genre. The solution is to create a curried function. |
| 62 | +```java |
| 63 | +static Function<Genre, Function<String, Function<String, Function<LocalDate, Book>>>> book_creator |
| 64 | + = genre |
| 65 | + -> author |
| 66 | + -> title |
| 67 | + -> publicationDate |
| 68 | + -> new Book(genre, author, title, publicationDate); |
| 69 | +``` |
| 70 | +Note that the order of the parameters is important. `genre` must come before `author`, `author` must come before `title` and so on. We must be considerate of this when writing curried functions to take full advantage of partial application. Using the above function, we can define a new function `fantasyBookFunc`, to generate `FANTASY` books as follows: |
| 71 | +```java |
| 72 | +Function<String, Function<String, Function<LocalDate, Book>>> fantasyBookFunc = Book.book_creator.apply(Genre.FANTASY); |
| 73 | +``` |
| 74 | +Unfortunately, the type signature of `BOOK_CREATOR` and `fantasyBookFunc` are difficult to read and understand. We can improve this by using the [builder pattern](https://java-design-patterns.com/patterns/builder/) and [functional interfaces](https://www.geeksforgeeks.org/functional-interfaces-java/#:~:text=A%20functional%20interface%20is%20an,any%20number%20of%20default%20methods). |
| 75 | +```java |
| 76 | +public static AddGenre builder() { |
| 77 | + return genre |
| 78 | + -> author |
| 79 | + -> title |
| 80 | + -> publicationDate |
| 81 | + -> new Book(genre, author, title, publicationDate); |
| 82 | +} |
| 83 | + |
| 84 | +public interface AddGenre { |
| 85 | +Book.AddAuthor withGenre(Genre genre); |
| 86 | +} |
| 87 | + |
| 88 | +public interface AddAuthor { |
| 89 | +Book.AddTitle withAuthor(String author); |
| 90 | +} |
| 91 | + |
| 92 | +public interface AddTitle { |
| 93 | +Book.AddPublicationDate withTitle(String title); |
| 94 | +} |
| 95 | + |
| 96 | +public interface AddPublicationDate { |
| 97 | +Book withPublicationDate(LocalDate publicationDate); |
| 98 | +} |
| 99 | +``` |
| 100 | +The semantics of the `builder` function can easily be understood. The `builder` function returns a function `AddGenre`, which adds the genre to the book. Similarity, the `AddGenre` function returns another function `AddTitle`, which adds the title to the book and so on, until the `AddPublicationDate` function returns a `Book`. |
| 101 | +For example, we could create a `Book` as follows: |
| 102 | +```java |
| 103 | +Book book = Book.builder().withGenre(Genre.FANTAST) |
| 104 | + .withAuthor("Author") |
| 105 | + .withTitle("Title") |
| 106 | + .withPublicationDate(LocalDate.of(2000, 7, 2)); |
| 107 | +``` |
| 108 | +The below example demonstrates how partial application can be used with the `builder` function to create specialised book builder functions. |
| 109 | +```java |
| 110 | +public static void main(String[] args) { |
| 111 | + LOGGER.info("Librarian begins their work."); |
| 112 | + |
| 113 | + // Defining genre book functions |
| 114 | + Book.AddAuthor fantasyBookFunc = Book.builder().withGenre(Genre.FANTASY); |
| 115 | + Book.AddAuthor horrorBookFunc = Book.builder().withGenre(Genre.HORROR); |
| 116 | + Book.AddAuthor scifiBookFunc = Book.builder().withGenre(Genre.SCI_FI); |
| 117 | + |
| 118 | + // Defining author book functions |
| 119 | + Book.AddTitle kingFantasyBooksFunc = fantasyBookFunc.withAuthor("Stephen King"); |
| 120 | + Book.AddTitle kingHorrorBooksFunc = horrorBookFunc.withAuthor("Stephen King"); |
| 121 | + Book.AddTitle rowlingFantasyBooksFunc = fantasyBookFunc.withAuthor("J.K. Rowling"); |
| 122 | + |
| 123 | + // Creates books by Stephen King (horror and fantasy genres) |
| 124 | + Book shining = kingHorrorBooksFunc.withTitle("The Shining") |
| 125 | + .withPublicationDate(LocalDate.of(1977, 1, 28)); |
| 126 | + Book darkTower = kingFantasyBooksFunc.withTitle("The Dark Tower: Gunslinger") |
| 127 | + .withPublicationDate(LocalDate.of(1982, 6, 10)); |
| 128 | + |
| 129 | + // Creates fantasy books by J.K. Rowling |
| 130 | + Book chamberOfSecrets = rowlingFantasyBooksFunc.withTitle("Harry Potter and the Chamber of Secrets") |
| 131 | + .withPublicationDate(LocalDate.of(1998, 7, 2)); |
| 132 | + |
| 133 | + // Create sci-fi books |
| 134 | + Book dune = scifiBookFunc.withAuthor("Frank Herbert") |
| 135 | + .withTitle("Dune") |
| 136 | + .withPublicationDate(LocalDate.of(1965, 8, 1)); |
| 137 | + Book foundation = scifiBookFunc.withAuthor("Isaac Asimov") |
| 138 | + .withTitle("Foundation") |
| 139 | + .withPublicationDate(LocalDate.of(1942, 5, 1)); |
| 140 | + |
| 141 | + LOGGER.info("Stephen King Books:"); |
| 142 | + LOGGER.info(shining.toString()); |
| 143 | + LOGGER.info(darkTower.toString()); |
| 144 | + |
| 145 | + LOGGER.info("J.K. Rowling Books:"); |
| 146 | + LOGGER.info(chamberOfSecrets.toString()); |
| 147 | + |
| 148 | + LOGGER.info("Sci-fi Books:"); |
| 149 | + LOGGER.info(dune.toString()); |
| 150 | + LOGGER.info(foundation.toString()); |
| 151 | +} |
| 152 | +``` |
| 153 | +Program output: |
| 154 | +``` |
| 155 | +Librarian begins their work. |
| 156 | +Stephen King Books: |
| 157 | +Book{genre=HORROR, author='Stephen King', title='The Shining', publicationDate=1977-01-28} |
| 158 | +Book{genre=FANTASY, author='Stephen King', title='The Dark Tower: Gunslinger', publicationDate=1982-06-10} |
| 159 | +J.K. Rowling Books: |
| 160 | +Book{genre=FANTASY, author='J.K. Rowling', title='Harry Potter and the Chamber of Secrets', publicationDate=1998-07-02} |
| 161 | +Sci-fi Books: |
| 162 | +Book{genre=SCI_FI, author='Frank Herbert', title='Dune', publicationDate=1965-08-01} |
| 163 | +Book{genre=SCI_FI, author='Isaac Asimov', title='Foundation', publicationDate=1942-05-01} |
| 164 | +``` |
| 165 | + |
| 166 | +## Class diagram |
| 167 | + |
| 168 | + |
| 169 | +## Applicability |
| 170 | +A curried function which has only been passed some of its arguments is called a partial application. Partial application |
| 171 | +allows for the creation of functions with some pre-defined data in their scope, since partial application can be used to |
| 172 | +create specialised functions with lower arity. This abstraction can help keep code readable and concise. Therefore, currying is useful when frequently calling functions with fixed parameters. |
| 173 | + |
| 174 | +## Known uses |
| 175 | +Most functional programming languages support curried functions. A popular example is [Haskell](https://www.haskell.org/), in which all functions are considered curried. |
| 176 | + |
| 177 | +## Consequences |
| 178 | +Pros |
| 179 | +* Currying allows for partial application, which can be used to create specialised functions concisely. |
| 180 | + |
| 181 | +Cons |
| 182 | +* The order of the parameters in a curried function is important since we want to take advantage of partial application. It is best to input the most general parameters first and input specific parameters last. |
| 183 | +* As shown in the programmatic example above, curried functions with several parameters have a cumbersome type signature (in Java). |
| 184 | + |
| 185 | +## Related patterns |
| 186 | +* [Builder patter](https://java-design-patterns.com/patterns/builder/) |
| 187 | + |
| 188 | +## Credits |
| 189 | +* [Currying in Java](https://www.baeldung.com/java-currying) |
| 190 | +* [What Is Currying in Programming](https://towardsdatascience.com/what-is-currying-in-programming-56fd57103431#:~:text=Currying%20is%20helpful%20when%20you,concise%2C%20and%20more%20readable%20solution.) |
| 191 | +* [Why the fudge should I use currying?](https://medium.com/dailyjs/why-the-fudge-should-i-use-currying-84e4000c8743) |
0 commit comments