From 2228212c2361c3ed54babe2b3778f386f5aac9d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sat, 30 Mar 2024 14:44:43 +0200 Subject: [PATCH] docs: explanation for combinator (#2877) --- combinator/README.md | 277 ++++++++++-------- .../com/iluwatar/combinator/FinderTest.java | 9 +- .../com/iluwatar/combinator/FindersTest.java | 40 +-- 3 files changed, 179 insertions(+), 147 deletions(-) diff --git a/combinator/README.md b/combinator/README.md index 15ec1ea59..70b6a82f7 100644 --- a/combinator/README.md +++ b/combinator/README.md @@ -1,132 +1,136 @@ --- title: Combinator -category: Idiom +category: Functional language: en tag: - - Reactive + - Idiom + - Reactive --- ## Also known as -Composition pattern +* Function Composition +* Functional Combinator ## Intent -The functional pattern representing a style of organizing libraries centered around the idea of combining functions. -Putting it simply, there is some type T, some functions for constructing “primitive” values of type T, and some “combinators” which can combine values of type T in various ways to build up more complex values of type T. +The Combinator pattern is intended to enable complex functionalities by combining simple functions into more complex +ones. It aims to achieve modularization and reusability by breaking down a task into simpler, interchangeable components +that can be composed in various ways. ## Explanation Real world example -> In computer science, combinatory logic is used as a simplified model of computation, used in computability theory and proof theory. Despite its simplicity, combinatory logic captures many essential features of computation. -> +> In computer science, combinatory logic is used as a simplified model of computation, used in computability theory and +> proof theory. Despite its simplicity, combinatory logic captures many essential features of computation. In plain words -> The combinator allows you to create new "things" from previously defined "things". -> +> The combinator allows you to create new "things" from previously defined "things" Wikipedia says -> A combinator is a higher-order function that uses only function application and earlier defined combinators to define a result from its arguments. -> +> A combinator is a higher-order function that uses only function application and earlier defined combinators to define +> a result from its arguments. **Programmatic Example** -Translating the combinator example above. First of all, we have a interface consist of several methods `contains`, `not`, `or`, `and` . +Translating the combinator example above. First of all, we have an interface consist of several +methods `contains`, `not`, `or`, `and` . ```java // Functional interface to find lines in text. public interface Finder { - // The function to find lines in text. - List find(String text); + // The function to find lines in text. + List find(String text); - // Simple implementation of function {@link #find(String)}. - static Finder contains(String word) { - return txt -> Stream.of(txt.split("\n")) - .filter(line -> line.toLowerCase().contains(word.toLowerCase())) - .collect(Collectors.toList()); - } + // Simple implementation of function {@link #find(String)}. + static Finder contains(String word) { + return txt -> Stream.of(txt.split("\n")) + .filter(line -> line.toLowerCase().contains(word.toLowerCase())) + .collect(Collectors.toList()); + } - // combinator not. - default Finder not(Finder notFinder) { - return txt -> { - List res = this.find(txt); - res.removeAll(notFinder.find(txt)); - return res; - }; - } + // combinator not. + default Finder not(Finder notFinder) { + return txt -> { + List res = this.find(txt); + res.removeAll(notFinder.find(txt)); + return res; + }; + } - // combinator or. - default Finder or(Finder orFinder) { - return txt -> { - List res = this.find(txt); - res.addAll(orFinder.find(txt)); - return res; - }; - } + // combinator or. + default Finder or(Finder orFinder) { + return txt -> { + List res = this.find(txt); + res.addAll(orFinder.find(txt)); + return res; + }; + } - // combinator and. - default Finder and(Finder andFinder) { - return - txt -> this - .find(txt) - .stream() - .flatMap(line -> andFinder.find(line).stream()) - .collect(Collectors.toList()); - } + // combinator and. + default Finder and(Finder andFinder) { + return + txt -> this + .find(txt) + .stream() + .flatMap(line -> andFinder.find(line).stream()) + .collect(Collectors.toList()); + } ... } ``` -Then we have also another combinator for some complex finders `advancedFinder`, `filteredFinder`, `specializedFinder` and `expandedFinder`. +Then we have also another combinator for some complex finders `advancedFinder`, `filteredFinder`, `specializedFinder` +and `expandedFinder`. ```java // Complex finders consisting of simple finder. public class Finders { - private Finders() { - } + private Finders() { + } - // Finder to find a complex query. - public static Finder advancedFinder(String query, String orQuery, String notQuery) { - return - Finder.contains(query) - .or(Finder.contains(orQuery)) - .not(Finder.contains(notQuery)); - } + // Finder to find a complex query. + public static Finder advancedFinder(String query, String orQuery, String notQuery) { + return + Finder.contains(query) + .or(Finder.contains(orQuery)) + .not(Finder.contains(notQuery)); + } - // Filtered finder looking a query with excluded queries as well. - public static Finder filteredFinder(String query, String... excludeQueries) { - var finder = Finder.contains(query); + // Filtered finder looking a query with excluded queries as well. + public static Finder filteredFinder(String query, String... excludeQueries) { + var finder = Finder.contains(query); - for (String q : excludeQueries) { - finder = finder.not(Finder.contains(q)); - } - return finder; - } + for (String q : excludeQueries) { + finder = finder.not(Finder.contains(q)); + } + return finder; + } - // Specialized query. Every next query is looked in previous result. - public static Finder specializedFinder(String... queries) { - var finder = identMult(); + // Specialized query. Every next query is looked in previous result. + public static Finder specializedFinder(String... queries) { + var finder = identMult(); - for (String query : queries) { - finder = finder.and(Finder.contains(query)); - } - return finder; - } + for (String query : queries) { + finder = finder.and(Finder.contains(query)); + } + return finder; + } - // Expanded query. Looking for alternatives. - public static Finder expandedFinder(String... queries) { - var finder = identSum(); + // Expanded query. Looking for alternatives. + public static Finder expandedFinder(String... queries) { + var finder = identSum(); - for (String query : queries) { - finder = finder.or(Finder.contains(query)); - } - return finder; - } + for (String query : queries) { + finder = finder.or(Finder.contains(query)); + } + return finder; + } ... } ``` @@ -134,75 +138,102 @@ public class Finders { Now we have created the interface and methods for combinators. Now we have an application working on these combinators. ```java -var queriesOr = new String[]{"many", "Annabel"}; -var finder = Finders.expandedFinder(queriesOr); -var res = finder.find(text()); -LOGGER.info("the result of expanded(or) query[{}] is {}", queriesOr, res); +var queriesOr=new String[]{"many","Annabel"}; + var finder=Finders.expandedFinder(queriesOr); + var res=finder.find(text()); + LOGGER.info("the result of expanded(or) query[{}] is {}",queriesOr,res); -var queriesAnd = new String[]{"Annabel", "my"}; -finder = Finders.specializedFinder(queriesAnd); -res = finder.find(text()); -LOGGER.info("the result of specialized(and) query[{}] is {}", queriesAnd, res); + var queriesAnd=new String[]{"Annabel","my"}; + finder=Finders.specializedFinder(queriesAnd); + res=finder.find(text()); + LOGGER.info("the result of specialized(and) query[{}] is {}",queriesAnd,res); -finder = Finders.advancedFinder("it was", "kingdom", "sea"); -res = finder.find(text()); -LOGGER.info("the result of advanced query is {}", res); + finder=Finders.advancedFinder("it was","kingdom","sea"); + res=finder.find(text()); + LOGGER.info("the result of advanced query is {}",res); -res = Finders.filteredFinder(" was ", "many", "child").find(text()); -LOGGER.info("the result of filtered query is {}", res); + res=Finders.filteredFinder(" was ","many","child").find(text()); + LOGGER.info("the result of filtered query is {}",res); -private static String text() { - return +private static String text(){ + return "It was many and many a year ago,\n" - + "In a kingdom by the sea,\n" - + "That a maiden there lived whom you may know\n" - + "By the name of ANNABEL LEE;\n" - + "And this maiden she lived with no other thought\n" - + "Than to love and be loved by me.\n" - + "I was a child and she was a child,\n" - + "In this kingdom by the sea;\n" - + "But we loved with a love that was more than love-\n" - + "I and my Annabel Lee;\n" - + "With a love that the winged seraphs of heaven\n" - + "Coveted her and me."; - } + +"In a kingdom by the sea,\n" + +"That a maiden there lived whom you may know\n" + +"By the name of ANNABEL LEE;\n" + +"And this maiden she lived with no other thought\n" + +"Than to love and be loved by me.\n" + +"I was a child and she was a child,\n" + +"In this kingdom by the sea;\n" + +"But we loved with a love that was more than love-\n" + +"I and my Annabel Lee;\n" + +"With a love that the winged seraphs of heaven\n" + +"Coveted her and me."; + } ``` **Program output:** ```java -the result of expanded(or) query[[many, Annabel]] is [It was many and many a year ago,, By the name of ANNABEL LEE;, I and my Annabel Lee;] -the result of specialized(and) query[[Annabel, my]] is [I and my Annabel Lee;] -the result of advanced query is [It was many and many a year ago,] -the result of filtered query is [But we loved with a love that was more than love-] +the result of expanded(or)query[[many,Annabel]]is[It was many and many a year ago,,By the name of ANNABEL LEE;,I and my Annabel Lee;] + the result of specialized(and)query[[Annabel,my]]is[I and my Annabel Lee;] + the result of advanced query is[It was many and many a year ago,] + the result of filtered query is[But we loved with a love that was more than love-] ``` -Now we can design our app to with the queries finding feature `expandedFinder`, `specializedFinder`, `advancedFinder`, `filteredFinder` which are all derived from `contains`, `or`, `not`, `and`. - +Now we can design our app to with the queries finding +feature `expandedFinder`, `specializedFinder`, `advancedFinder`, `filteredFinder` which are all derived +from `contains`, `or`, `not`, `and`. ## Class diagram + ![alt text](./etc/combinator.urm.png "Combinator class diagram") ## Applicability -Use the combinator pattern when: -- You are able to create a more complex value from more plain values but having the same type(a combination of them) +This pattern is applicable in scenarios where: -## Benefits +* The solution to a problem can be constructed from simple, reusable components. +* There is a need for high modularity and reusability of functions. +* The programming environment supports first-class functions and higher-order functions. -- From a developers perspective the API is made of terms from the domain. -- There is a clear distinction between combining and application phase. -- One first constructs an instance and then executes it. -- This makes the pattern applicable in a parallel environment. +## Known Uses +* Functional programming languages like Haskell and Scala extensively use combinators for tasks ranging from parsing to + UI construction. +* In domain-specific languages, particularly those involved in parsing, such as parsing expression grammars. +* In libraries for functional programming in languages like JavaScript, Python, and Ruby. +* java.util.function.Function#compose +* java.util.function.Function#andThen -## Real world examples +## Consequences -- java.util.function.Function#compose -- java.util.function.Function#andThen +Benefits: + +* Enhances modularity and reusability by breaking down complex tasks into simpler, composable functions. +* Promotes readability and maintainability by using a declarative style of programming. +* Facilitates lazy evaluation and potentially more efficient execution through function composition. + +Trade-offs: + +* Can lead to a steep learning curve for those unfamiliar with functional programming principles. +* May result in performance overhead due to the creation of intermediate functions. +* Debugging can be challenging due to the abstract nature of function compositions. + +## Related Patterns + +[Strategy](https://java-design-patterns.com/patterns/strategy/): Both involve selecting an algorithm at runtime, but +Combinator uses composition of functions. +[Decorator](https://java-design-patterns.com/patterns/decorator/): Similar to Combinator in enhancing functionality, but +Decorator focuses on object augmentation. +[Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/): Relies on chaining +objects, whereas Combinator chains functions. ## Credits -- [Example for java](https://gtrefs.github.io/code/combinator-pattern/) +- [Example for Java](https://gtrefs.github.io/code/combinator-pattern/) - [Combinator pattern](https://wiki.haskell.org/Combinator_pattern) - [Combinatory logic](https://wiki.haskell.org/Combinatory_logic) +- [Structure and Interpretation of Computer Programs](https://amzn.to/3PJwVsf) +- [Functional Programming in Scala](https://amzn.to/4cEo6K2) +- [Haskell: The Craft of Functional Programming](https://amzn.to/4axxtcF) diff --git a/combinator/src/test/java/com/iluwatar/combinator/FinderTest.java b/combinator/src/test/java/com/iluwatar/combinator/FinderTest.java index 4f0d42258..c4a505df0 100644 --- a/combinator/src/test/java/com/iluwatar/combinator/FinderTest.java +++ b/combinator/src/test/java/com/iluwatar/combinator/FinderTest.java @@ -22,6 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.combinator; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -33,12 +34,12 @@ class FinderTest { @Test void contains() { var example = """ - the first one - the second one\s - """; + the first one + the second one\s + """; var result = Finder.contains("second").find(example); assertEquals(1, result.size()); - assertEquals( "the second one ", result.get(0)); + assertEquals("the second one ", result.get(0)); } } diff --git a/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java b/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java index bf34af170..aa84b9350 100644 --- a/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java +++ b/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java @@ -22,6 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.combinator; import static com.iluwatar.combinator.Finders.advancedFinder; @@ -38,48 +39,47 @@ class FindersTest { void advancedFinderTest() { var res = advancedFinder("it was", "kingdom", "sea").find(text()); assertEquals(1, res.size()); - assertEquals( "It was many and many a year ago,", res.get(0)); + assertEquals("It was many and many a year ago,", res.get(0)); } @Test void filteredFinderTest() { var res = filteredFinder(" was ", "many", "child").find(text()); assertEquals(1, res.size()); - assertEquals( "But we loved with a love that was more than love-", res.get(0)); + assertEquals("But we loved with a love that was more than love-", res.get(0)); } @Test void specializedFinderTest() { var res = specializedFinder("love", "heaven").find(text()); assertEquals(1, res.size()); - assertEquals( "With a love that the winged seraphs of heaven", res.get(0)); + assertEquals("With a love that the winged seraphs of heaven", res.get(0)); } @Test void expandedFinderTest() { var res = expandedFinder("It was", "kingdom").find(text()); assertEquals(3, res.size()); - assertEquals( "It was many and many a year ago,", res.get(0)); - assertEquals( "In a kingdom by the sea,", res.get(1)); - assertEquals( "In this kingdom by the sea;", res.get(2)); + assertEquals("It was many and many a year ago,", res.get(0)); + assertEquals("In a kingdom by the sea,", res.get(1)); + assertEquals("In this kingdom by the sea;", res.get(2)); } private String text() { - return - """ - It was many and many a year ago, - In a kingdom by the sea, - That a maiden there lived whom you may know - By the name of ANNABEL LEE; - And this maiden she lived with no other thought - Than to love and be loved by me. - I was a child and she was a child, - In this kingdom by the sea; - But we loved with a love that was more than love- - I and my Annabel Lee; - With a love that the winged seraphs of heaven - Coveted her and me."""; + return """ + It was many and many a year ago, + In a kingdom by the sea, + That a maiden there lived whom you may know + By the name of ANNABEL LEE; + And this maiden she lived with no other thought + Than to love and be loved by me. + I was a child and she was a child, + In this kingdom by the sea; + But we loved with a love that was more than love- + I and my Annabel Lee; + With a love that the winged seraphs of heaven + Coveted her and me."""; } }