Skip to content

Commit 7be2828

Browse files
hkattthkattt-anu
andauthored
feature: iluwatar#1842 Add Currying Design Pattern (iluwatar#2271)
* iluwatar#1842 Setting up project and creating example classes. Issues running site and deploy * iluwatar#1842 Added unit tests * iluwatar#1842 Improved example * iluwatar#1842 Added UML class diagram * iluwatar#1842 Added comments to Genre class * iluwatar#1842 Improved readability of lambda function * iluwatar#1842 Started working on the README and created initial UML * iluwatar#1842 Added example to README * iluwatar#1842 Replaced prints with LOGGER * iluwatar#1842 Fixed typo in README * iluwatar#1842 Testing commit account * iluwatar#1842 Adding documentation to App class * iluwatar#1842 Improved documentation * iluwatar#1842 Added documentation to AppTest * iluwatar#1842 Fixing latex formating issue * iluwatar#1842 Improving the intent description * iluwatar#1842 Removed override methods from the UML diagram for clarity * iluwatar#1842 Renamed the SCI_FI enum * iluwatar#1842 Updated the currying pom.xml * iluwatar#1842 Removed unneeded comment * iluwatar#1842 Improving documentation and README * Added review changes. * Fixing build issues and added javadoc comments to functional interfaces. * Removing code smells * Removed unnecessary toString method * Using lombok to reduce boiler plate. * Fixed frontmatter. * Removing function name code smell * Fixed README typo * Added book_creator test to improve coverage Co-authored-by: Hugo Kat <[email protected]>
1 parent 2d309b8 commit 7be2828

File tree

10 files changed

+656
-0
lines changed

10 files changed

+656
-0
lines changed

currying/README.md

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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+
![currying-uml](./etc/currying.urm.png)
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)

currying/etc/currying.urm.png

40.7 KB
Loading

currying/etc/currying.urm.puml

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
@startuml
2+
package com.iluwatar.currying {
3+
class App {
4+
+ App()
5+
+ main(args : String[]) {static}
6+
}
7+
class Book {
8+
~ BOOK_CREATOR : Function<Genre, Function<String, Function<String, Function<LocalDate, Book>>>> {static}
9+
- author : String
10+
- genre : Genre
11+
- publicationDate : LocalDate
12+
- title : String
13+
~ Book(genre : Genre, author : String, title : String, publicationDate : LocalDate)
14+
+ builder() : AddGenre {static}
15+
}
16+
interface AddAuthor {
17+
+ withAuthor(String) : AddTitle {abstract}
18+
}
19+
interface AddGenre {
20+
+ withGenre(Genre) : AddAuthor {abstract}
21+
}
22+
interface AddPublicationDate {
23+
+ withPublicationDate(LocalDate) : Book {abstract}
24+
}
25+
interface AddTitle {
26+
+ withTitle(String) : AddPublicationDate {abstract}
27+
}
28+
enum Genre {
29+
+ FANTASY {static}
30+
+ HORROR {static}
31+
+ SCI_FI {static}
32+
}
33+
}
34+
Book --> "-genre" Genre
35+
AddPublicationDate ..+ Book
36+
AddAuthor ..+ Book
37+
AddTitle ..+ Book
38+
AddGenre ..+ Book
39+
@enduml

currying/pom.xml

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
5+
6+
The MIT License
7+
Copyright © 2014-2022 Ilkka Seppälä
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in
17+
all copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
27+
-->
28+
<project xmlns="http://maven.apache.org/POM/4.0.0"
29+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
30+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
31+
<modelVersion>4.0.0</modelVersion>
32+
<parent>
33+
<groupId>com.iluwatar</groupId>
34+
<artifactId>java-design-patterns</artifactId>
35+
<version>1.26.0-SNAPSHOT</version>
36+
</parent>
37+
<artifactId>currying</artifactId>
38+
<dependencies>
39+
<dependency>
40+
<groupId>org.junit.jupiter</groupId>
41+
<artifactId>junit-jupiter-engine</artifactId>
42+
<scope>test</scope>
43+
</dependency>
44+
</dependencies>
45+
<build>
46+
<plugins>
47+
<!-- Maven assembly plugin is invoked with default setting which we have
48+
in parent pom and specifying the class having main method -->
49+
<plugin>
50+
<groupId>org.apache.maven.plugins</groupId>
51+
<artifactId>maven-assembly-plugin</artifactId>
52+
<executions>
53+
<execution>
54+
<configuration>
55+
<archive>
56+
<manifest>
57+
<mainClass>com.iluwatar.currying.App</mainClass>
58+
</manifest>
59+
</archive>
60+
</configuration>
61+
</execution>
62+
</executions>
63+
</plugin>
64+
</plugins>
65+
</build>
66+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
3+
*
4+
* The MIT License
5+
* Copyright © 2014-2022 Ilkka Seppälä
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
package com.iluwatar.currying;
26+
27+
import java.time.LocalDate;
28+
import lombok.extern.slf4j.Slf4j;
29+
30+
/**
31+
* Currying decomposes a function with multiple arguments in multiple functions that
32+
* take a single argument. A curried function which has only been passed some of its
33+
* arguments is called a partial application. Partial application is useful since it can
34+
* be used to create specialised functions in a concise way.
35+
*
36+
* <p>In this example, a librarian uses a curried book builder function create books belonging to
37+
* desired genres and written by specific authors.
38+
*/
39+
@Slf4j
40+
public class App {
41+
/**
42+
* Main entry point of the program.
43+
*/
44+
public static void main(String[] args) {
45+
LOGGER.info("Librarian begins their work.");
46+
47+
// Defining genre book functions
48+
Book.AddAuthor fantasyBookFunc = Book.builder().withGenre(Genre.FANTASY);
49+
Book.AddAuthor horrorBookFunc = Book.builder().withGenre(Genre.HORROR);
50+
Book.AddAuthor scifiBookFunc = Book.builder().withGenre(Genre.SCIFI);
51+
52+
// Defining author book functions
53+
Book.AddTitle kingFantasyBooksFunc = fantasyBookFunc.withAuthor("Stephen King");
54+
Book.AddTitle kingHorrorBooksFunc = horrorBookFunc.withAuthor("Stephen King");
55+
Book.AddTitle rowlingFantasyBooksFunc = fantasyBookFunc.withAuthor("J.K. Rowling");
56+
57+
// Creates books by Stephen King (horror and fantasy genres)
58+
Book shining = kingHorrorBooksFunc.withTitle("The Shining")
59+
.withPublicationDate(LocalDate.of(1977, 1, 28));
60+
Book darkTower = kingFantasyBooksFunc.withTitle("The Dark Tower: Gunslinger")
61+
.withPublicationDate(LocalDate.of(1982, 6, 10));
62+
63+
// Creates fantasy books by J.K. Rowling
64+
Book chamberOfSecrets = rowlingFantasyBooksFunc.withTitle("Harry Potter and the Chamber of Secrets")
65+
.withPublicationDate(LocalDate.of(1998, 7, 2));
66+
67+
// Create sci-fi books
68+
Book dune = scifiBookFunc.withAuthor("Frank Herbert")
69+
.withTitle("Dune")
70+
.withPublicationDate(LocalDate.of(1965, 8, 1));
71+
Book foundation = scifiBookFunc.withAuthor("Isaac Asimov")
72+
.withTitle("Foundation")
73+
.withPublicationDate(LocalDate.of(1942, 5, 1));
74+
75+
LOGGER.info("Stephen King Books:");
76+
LOGGER.info(shining.toString());
77+
LOGGER.info(darkTower.toString());
78+
79+
LOGGER.info("J.K. Rowling Books:");
80+
LOGGER.info(chamberOfSecrets.toString());
81+
82+
LOGGER.info("Sci-fi Books:");
83+
LOGGER.info(dune.toString());
84+
LOGGER.info(foundation.toString());
85+
}
86+
}

0 commit comments

Comments
 (0)