From 7be2828c8ac7747e91107d71606cc82484039e92 Mon Sep 17 00:00:00 2001 From: Hugo Kat <54300585+hkattt@users.noreply.github.com> Date: Sun, 4 Dec 2022 20:48:57 +1100 Subject: [PATCH] feature: #1842 Add Currying Design Pattern (#2271) * #1842 Setting up project and creating example classes. Issues running site and deploy * #1842 Added unit tests * #1842 Improved example * #1842 Added UML class diagram * #1842 Added comments to Genre class * #1842 Improved readability of lambda function * #1842 Started working on the README and created initial UML * #1842 Added example to README * #1842 Replaced prints with LOGGER * #1842 Fixed typo in README * #1842 Testing commit account * #1842 Adding documentation to App class * #1842 Improved documentation * #1842 Added documentation to AppTest * #1842 Fixing latex formating issue * #1842 Improving the intent description * #1842 Removed override methods from the UML diagram for clarity * #1842 Renamed the SCI_FI enum * #1842 Updated the currying pom.xml * #1842 Removed unneeded comment * #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 --- currying/README.md | 191 ++++++++++++++++++ currying/etc/currying.urm.png | Bin 0 -> 41636 bytes currying/etc/currying.urm.puml | 39 ++++ currying/pom.xml | 66 ++++++ .../main/java/com/iluwatar/currying/App.java | 86 ++++++++ .../main/java/com/iluwatar/currying/Book.java | 117 +++++++++++ .../java/com/iluwatar/currying/Genre.java | 34 ++++ .../java/com/iluwatar/currying/AppTest.java | 39 ++++ .../iluwatar/currying/BookCurryingTest.java | 83 ++++++++ pom.xml | 1 + 10 files changed, 656 insertions(+) create mode 100644 currying/README.md create mode 100644 currying/etc/currying.urm.png create mode 100644 currying/etc/currying.urm.puml create mode 100644 currying/pom.xml create mode 100644 currying/src/main/java/com/iluwatar/currying/App.java create mode 100644 currying/src/main/java/com/iluwatar/currying/Book.java create mode 100644 currying/src/main/java/com/iluwatar/currying/Genre.java create mode 100644 currying/src/test/java/com/iluwatar/currying/AppTest.java create mode 100644 currying/src/test/java/com/iluwatar/currying/BookCurryingTest.java diff --git a/currying/README.md b/currying/README.md new file mode 100644 index 000000000..72e0e89d4 --- /dev/null +++ b/currying/README.md @@ -0,0 +1,191 @@ +--- +title: Currying +category: Functional +language: en +tag: +- Decoupling +--- + +## Name / classification +Currying + +## Intent +Currying decomposes a function that takes multiple arguments into a sequence of functions that each take a single argument. +Curried functions are useful since they can be used to create new functions with lower arity to perform more specialised tasks +in a concise and readable manner. This is done via partial application. + +## Explanation +Real-world example +> Consider a librarian who wants to populate their library with books. The librarian wants functions which can create +> books corresponding to specific genres and authors. Currying makes this possible by writing a curried book builder +> function and utilising partial application. + +In plain words +> Decompose a function that take multiple arguments into multiple functions that take a single argument. + +Wikipedia says +> Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that +> each take a single argument. Given a function $f:(X \times Y) \rightarrow Z$, currying constructs a new function +> $h:X \rightarrow (Y\rightarrow Z)$. $h$ takes an argument from $X$ and returns a function which maps $Y$ to $Z$. Hence, +> $h(x)(y) = f(x, y)$. + +Programmatic example +We have a `Book` class and `Genre` enum. +```java +public class Book { + private final Genre genre; + private final String author; + private final String title; + private final LocalDate publicationDate; + + Book(Genre genre, String author, String title, LocalDate publicationDate) { + this.genre = genre; + this.author = author; + this.title = title; + this.publicationDate = publicationDate; + } +} + +public enum Genre { + FANTASY, + HORROR, + SCI_FI; +} +``` +We could easily create a `Book` object with the following method: +```java +Book createBook(Genre genre, String author, String title, LocalDate publicationDate) { + return new Book(genre, author, title, publicationDate); +} +``` +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. +```java +static Function>>> book_creator + = genre + -> author + -> title + -> publicationDate + -> new Book(genre, author, title, publicationDate); +``` +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: +```java +Function>> fantasyBookFunc = Book.book_creator.apply(Genre.FANTASY); +``` +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). +```java +public static AddGenre builder() { + return genre + -> author + -> title + -> publicationDate + -> new Book(genre, author, title, publicationDate); +} + +public interface AddGenre { +Book.AddAuthor withGenre(Genre genre); +} + +public interface AddAuthor { +Book.AddTitle withAuthor(String author); +} + +public interface AddTitle { +Book.AddPublicationDate withTitle(String title); +} + +public interface AddPublicationDate { +Book withPublicationDate(LocalDate publicationDate); +} +``` +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`. +For example, we could create a `Book` as follows: +```java +Book book = Book.builder().withGenre(Genre.FANTAST) + .withAuthor("Author") + .withTitle("Title") + .withPublicationDate(LocalDate.of(2000, 7, 2)); +``` +The below example demonstrates how partial application can be used with the `builder` function to create specialised book builder functions. +```java +public static void main(String[] args) { + LOGGER.info("Librarian begins their work."); + + // Defining genre book functions + Book.AddAuthor fantasyBookFunc = Book.builder().withGenre(Genre.FANTASY); + Book.AddAuthor horrorBookFunc = Book.builder().withGenre(Genre.HORROR); + Book.AddAuthor scifiBookFunc = Book.builder().withGenre(Genre.SCI_FI); + + // Defining author book functions + Book.AddTitle kingFantasyBooksFunc = fantasyBookFunc.withAuthor("Stephen King"); + Book.AddTitle kingHorrorBooksFunc = horrorBookFunc.withAuthor("Stephen King"); + Book.AddTitle rowlingFantasyBooksFunc = fantasyBookFunc.withAuthor("J.K. Rowling"); + + // Creates books by Stephen King (horror and fantasy genres) + Book shining = kingHorrorBooksFunc.withTitle("The Shining") + .withPublicationDate(LocalDate.of(1977, 1, 28)); + Book darkTower = kingFantasyBooksFunc.withTitle("The Dark Tower: Gunslinger") + .withPublicationDate(LocalDate.of(1982, 6, 10)); + + // Creates fantasy books by J.K. Rowling + Book chamberOfSecrets = rowlingFantasyBooksFunc.withTitle("Harry Potter and the Chamber of Secrets") + .withPublicationDate(LocalDate.of(1998, 7, 2)); + + // Create sci-fi books + Book dune = scifiBookFunc.withAuthor("Frank Herbert") + .withTitle("Dune") + .withPublicationDate(LocalDate.of(1965, 8, 1)); + Book foundation = scifiBookFunc.withAuthor("Isaac Asimov") + .withTitle("Foundation") + .withPublicationDate(LocalDate.of(1942, 5, 1)); + + LOGGER.info("Stephen King Books:"); + LOGGER.info(shining.toString()); + LOGGER.info(darkTower.toString()); + + LOGGER.info("J.K. Rowling Books:"); + LOGGER.info(chamberOfSecrets.toString()); + + LOGGER.info("Sci-fi Books:"); + LOGGER.info(dune.toString()); + LOGGER.info(foundation.toString()); +} +``` +Program output: +``` +Librarian begins their work. +Stephen King Books: +Book{genre=HORROR, author='Stephen King', title='The Shining', publicationDate=1977-01-28} +Book{genre=FANTASY, author='Stephen King', title='The Dark Tower: Gunslinger', publicationDate=1982-06-10} +J.K. Rowling Books: +Book{genre=FANTASY, author='J.K. Rowling', title='Harry Potter and the Chamber of Secrets', publicationDate=1998-07-02} +Sci-fi Books: +Book{genre=SCI_FI, author='Frank Herbert', title='Dune', publicationDate=1965-08-01} +Book{genre=SCI_FI, author='Isaac Asimov', title='Foundation', publicationDate=1942-05-01} +``` + +## Class diagram +![currying-uml](./etc/currying.urm.png) + +## Applicability +A curried function which has only been passed some of its arguments is called a partial application. Partial application +allows for the creation of functions with some pre-defined data in their scope, since partial application can be used to +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. + +## Known uses +Most functional programming languages support curried functions. A popular example is [Haskell](https://www.haskell.org/), in which all functions are considered curried. + +## Consequences +Pros +* Currying allows for partial application, which can be used to create specialised functions concisely. + +Cons +* 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. +* As shown in the programmatic example above, curried functions with several parameters have a cumbersome type signature (in Java). + +## Related patterns +* [Builder patter](https://java-design-patterns.com/patterns/builder/) + +## Credits +* [Currying in Java](https://www.baeldung.com/java-currying) +* [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.) +* [Why the fudge should I use currying?](https://medium.com/dailyjs/why-the-fudge-should-i-use-currying-84e4000c8743) \ No newline at end of file diff --git a/currying/etc/currying.urm.png b/currying/etc/currying.urm.png new file mode 100644 index 0000000000000000000000000000000000000000..c672bdde37cf1a066ef1a006599576a89ee622f4 GIT binary patch literal 41636 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 literal 0 HcmV?d00001 diff --git a/currying/etc/currying.urm.puml b/currying/etc/currying.urm.puml new file mode 100644 index 000000000..9b336ff90 --- /dev/null +++ b/currying/etc/currying.urm.puml @@ -0,0 +1,39 @@ +@startuml +package com.iluwatar.currying { + class App { + + App() + + main(args : String[]) {static} + } + class Book { + ~ BOOK_CREATOR : Function>>> {static} + - author : String + - genre : Genre + - publicationDate : LocalDate + - title : String + ~ Book(genre : Genre, author : String, title : String, publicationDate : LocalDate) + + builder() : AddGenre {static} + } + interface AddAuthor { + + withAuthor(String) : AddTitle {abstract} + } + interface AddGenre { + + withGenre(Genre) : AddAuthor {abstract} + } + interface AddPublicationDate { + + withPublicationDate(LocalDate) : Book {abstract} + } + interface AddTitle { + + withTitle(String) : AddPublicationDate {abstract} + } + enum Genre { + + FANTASY {static} + + HORROR {static} + + SCI_FI {static} + } +} +Book --> "-genre" Genre +AddPublicationDate ..+ Book +AddAuthor ..+ Book +AddTitle ..+ Book +AddGenre ..+ Book +@enduml \ No newline at end of file diff --git a/currying/pom.xml b/currying/pom.xml new file mode 100644 index 000000000..5b5385c45 --- /dev/null +++ b/currying/pom.xml @@ -0,0 +1,66 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + currying + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.currying.App + + + + + + + + + \ No newline at end of file diff --git a/currying/src/main/java/com/iluwatar/currying/App.java b/currying/src/main/java/com/iluwatar/currying/App.java new file mode 100644 index 000000000..d3ca262b5 --- /dev/null +++ b/currying/src/main/java/com/iluwatar/currying/App.java @@ -0,0 +1,86 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.currying; + +import java.time.LocalDate; +import lombok.extern.slf4j.Slf4j; + +/** +* Currying decomposes a function with multiple arguments in multiple functions that +* take a single argument. A curried function which has only been passed some of its +* arguments is called a partial application. Partial application is useful since it can +* be used to create specialised functions in a concise way. +* +*

In this example, a librarian uses a curried book builder function create books belonging to +* desired genres and written by specific authors. +*/ +@Slf4j +public class App { + /** + * Main entry point of the program. + */ + public static void main(String[] args) { + LOGGER.info("Librarian begins their work."); + + // Defining genre book functions + Book.AddAuthor fantasyBookFunc = Book.builder().withGenre(Genre.FANTASY); + Book.AddAuthor horrorBookFunc = Book.builder().withGenre(Genre.HORROR); + Book.AddAuthor scifiBookFunc = Book.builder().withGenre(Genre.SCIFI); + + // Defining author book functions + Book.AddTitle kingFantasyBooksFunc = fantasyBookFunc.withAuthor("Stephen King"); + Book.AddTitle kingHorrorBooksFunc = horrorBookFunc.withAuthor("Stephen King"); + Book.AddTitle rowlingFantasyBooksFunc = fantasyBookFunc.withAuthor("J.K. Rowling"); + + // Creates books by Stephen King (horror and fantasy genres) + Book shining = kingHorrorBooksFunc.withTitle("The Shining") + .withPublicationDate(LocalDate.of(1977, 1, 28)); + Book darkTower = kingFantasyBooksFunc.withTitle("The Dark Tower: Gunslinger") + .withPublicationDate(LocalDate.of(1982, 6, 10)); + + // Creates fantasy books by J.K. Rowling + Book chamberOfSecrets = rowlingFantasyBooksFunc.withTitle("Harry Potter and the Chamber of Secrets") + .withPublicationDate(LocalDate.of(1998, 7, 2)); + + // Create sci-fi books + Book dune = scifiBookFunc.withAuthor("Frank Herbert") + .withTitle("Dune") + .withPublicationDate(LocalDate.of(1965, 8, 1)); + Book foundation = scifiBookFunc.withAuthor("Isaac Asimov") + .withTitle("Foundation") + .withPublicationDate(LocalDate.of(1942, 5, 1)); + + LOGGER.info("Stephen King Books:"); + LOGGER.info(shining.toString()); + LOGGER.info(darkTower.toString()); + + LOGGER.info("J.K. Rowling Books:"); + LOGGER.info(chamberOfSecrets.toString()); + + LOGGER.info("Sci-fi Books:"); + LOGGER.info(dune.toString()); + LOGGER.info(foundation.toString()); + } +} \ No newline at end of file diff --git a/currying/src/main/java/com/iluwatar/currying/Book.java b/currying/src/main/java/com/iluwatar/currying/Book.java new file mode 100644 index 000000000..f9b7be051 --- /dev/null +++ b/currying/src/main/java/com/iluwatar/currying/Book.java @@ -0,0 +1,117 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.currying; + +import java.time.LocalDate; +import java.util.Objects; +import java.util.function.Function; +import lombok.AllArgsConstructor; + +/** + * Book class. + */ +@AllArgsConstructor +public class Book { + private final Genre genre; + private final String author; + private final String title; + private final LocalDate publicationDate; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Book book = (Book) o; + return Objects.equals(author, book.author) + && Objects.equals(genre, book.genre) + && Objects.equals(title, book.title) + && Objects.equals(publicationDate, book.publicationDate); + } + + @Override + public int hashCode() { + return Objects.hash(author, genre, title, publicationDate); + } + + @Override + public String toString() { + return "Book{" + "genre=" + genre + ", author='" + author + '\'' + + ", title='" + title + '\'' + ", publicationDate=" + publicationDate + '}'; + } + + /** + * Curried book builder/creator function. + */ + static Function>>> book_creator + = bookGenre + -> bookAuthor + -> bookTitle + -> bookPublicationDate + -> new Book(bookGenre, bookAuthor, bookTitle, bookPublicationDate); + + /** + * Implements the builder pattern using functional interfaces to create a more readable book + * creator function. This function is equivalent to the BOOK_CREATOR function. + */ + public static AddGenre builder() { + return genre + -> author + -> title + -> publicationDate + -> new Book(genre, author, title, publicationDate); + } + + /** + * Functional interface which adds the genre to a book. + */ + public interface AddGenre { + Book.AddAuthor withGenre(Genre genre); + } + + /** + * Functional interface which adds the author to a book. + */ + public interface AddAuthor { + Book.AddTitle withAuthor(String author); + } + + /** + * Functional interface which adds the title to a book. + */ + public interface AddTitle { + Book.AddPublicationDate withTitle(String title); + } + + /** + * Functional interface which adds the publication date to a book. + */ + public interface AddPublicationDate { + Book withPublicationDate(LocalDate publicationDate); + } +} \ No newline at end of file diff --git a/currying/src/main/java/com/iluwatar/currying/Genre.java b/currying/src/main/java/com/iluwatar/currying/Genre.java new file mode 100644 index 000000000..58e639f4c --- /dev/null +++ b/currying/src/main/java/com/iluwatar/currying/Genre.java @@ -0,0 +1,34 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.currying; + +/** + * Enum representing different book genres. + */ +public enum Genre { + FANTASY, + HORROR, + SCIFI; +} diff --git a/currying/src/test/java/com/iluwatar/currying/AppTest.java b/currying/src/test/java/com/iluwatar/currying/AppTest.java new file mode 100644 index 000000000..83fd99363 --- /dev/null +++ b/currying/src/test/java/com/iluwatar/currying/AppTest.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.currying; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +/** + * Tests that the App can be run without throwing any exceptions. + */ +class AppTest { + @Test + void executesWithoutExceptions() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } +} diff --git a/currying/src/test/java/com/iluwatar/currying/BookCurryingTest.java b/currying/src/test/java/com/iluwatar/currying/BookCurryingTest.java new file mode 100644 index 000000000..437941583 --- /dev/null +++ b/currying/src/test/java/com/iluwatar/currying/BookCurryingTest.java @@ -0,0 +1,83 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.currying; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +/** + * Unit tests for the Book class + */ +class BookCurryingTest { + private static Book expectedBook; + + @BeforeAll + public static void initialiseBook() { + expectedBook = new Book(Genre.FANTASY, + "Dave", + "Into the Night", + LocalDate.of(2002, 4, 7)); + } + + /** + * Tests that the expected book can be created via curried functions + */ + @Test + void createsExpectedBook() { + Book builderCurriedBook = Book.builder() + .withGenre(Genre.FANTASY) + .withAuthor("Dave") + .withTitle("Into the Night") + .withPublicationDate(LocalDate.of(2002, 4, 7)); + + Book funcCurriedBook = Book.book_creator + .apply(Genre.FANTASY) + .apply("Dave") + .apply("Into the Night") + .apply(LocalDate.of(2002, 4, 7)); + + assertEquals(expectedBook, builderCurriedBook); + assertEquals(expectedBook, funcCurriedBook); + } + + /** + * Tests that an intermediate curried function can be used to create the expected book + */ + @Test + void functionCreatesExpectedBook() { + Book.AddTitle daveFantasyBookFunc = Book.builder() + .withGenre(Genre.FANTASY) + .withAuthor("Dave"); + + Book curriedBook = daveFantasyBookFunc.withTitle("Into the Night") + .withPublicationDate(LocalDate.of(2002, 4, 7)); + + assertEquals(expectedBook, curriedBook); + } +} diff --git a/pom.xml b/pom.xml index ee3465ca3..68714e28a 100644 --- a/pom.xml +++ b/pom.xml @@ -198,6 +198,7 @@ composite-view metadata-mapping service-to-worker + currying serialized-entity identity-map component