mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-10-14 10:18:39 +00:00
docs: explanation for combinator (#2877)
This commit is contained in:
1 parent
30ac97fe59
commit
2228212c23
3 files changed
+179
-147
No files matched your search
+154
-123
@@ -1,132 +1,136 @@
|
|||||||
---
|
---
|
||||||
title: Combinator
|
title: Combinator
|
||||||
category: Idiom
|
category: Functional
|
||||||
language: en
|
language: en
|
||||||
tag:
|
tag:
|
||||||
- Reactive
|
- Idiom
|
||||||
|
- Reactive
|
||||||
---
|
---
|
||||||
|
|
||||||
## Also known as
|
## Also known as
|
||||||
|
|
||||||
Composition pattern
|
* Function Composition
|
||||||
|
* Functional Combinator
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
|
||||||
The functional pattern representing a style of organizing libraries centered around the idea of combining functions.
|
The Combinator pattern is intended to enable complex functionalities by combining simple functions into more complex
|
||||||
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.
|
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
|
## Explanation
|
||||||
|
|
||||||
Real world example
|
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
|
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
|
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**
|
**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
|
```java
|
||||||
// Functional interface to find lines in text.
|
// Functional interface to find lines in text.
|
||||||
public interface Finder {
|
public interface Finder {
|
||||||
|
|
||||||
// The function to find lines in text.
|
// The function to find lines in text.
|
||||||
List<String> find(String text);
|
List<String> find(String text);
|
||||||
|
|
||||||
// Simple implementation of function {@link #find(String)}.
|
// Simple implementation of function {@link #find(String)}.
|
||||||
static Finder contains(String word) {
|
static Finder contains(String word) {
|
||||||
return txt -> Stream.of(txt.split("\n"))
|
return txt -> Stream.of(txt.split("\n"))
|
||||||
.filter(line -> line.toLowerCase().contains(word.toLowerCase()))
|
.filter(line -> line.toLowerCase().contains(word.toLowerCase()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
// combinator not.
|
// combinator not.
|
||||||
default Finder not(Finder notFinder) {
|
default Finder not(Finder notFinder) {
|
||||||
return txt -> {
|
return txt -> {
|
||||||
List<String> res = this.find(txt);
|
List<String> res = this.find(txt);
|
||||||
res.removeAll(notFinder.find(txt));
|
res.removeAll(notFinder.find(txt));
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// combinator or.
|
// combinator or.
|
||||||
default Finder or(Finder orFinder) {
|
default Finder or(Finder orFinder) {
|
||||||
return txt -> {
|
return txt -> {
|
||||||
List<String> res = this.find(txt);
|
List<String> res = this.find(txt);
|
||||||
res.addAll(orFinder.find(txt));
|
res.addAll(orFinder.find(txt));
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// combinator and.
|
// combinator and.
|
||||||
default Finder and(Finder andFinder) {
|
default Finder and(Finder andFinder) {
|
||||||
return
|
return
|
||||||
txt -> this
|
txt -> this
|
||||||
.find(txt)
|
.find(txt)
|
||||||
.stream()
|
.stream()
|
||||||
.flatMap(line -> andFinder.find(line).stream())
|
.flatMap(line -> andFinder.find(line).stream())
|
||||||
.collect(Collectors.toList());
|
.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
|
```java
|
||||||
// Complex finders consisting of simple finder.
|
// Complex finders consisting of simple finder.
|
||||||
public class Finders {
|
public class Finders {
|
||||||
|
|
||||||
private Finders() {
|
private Finders() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finder to find a complex query.
|
// Finder to find a complex query.
|
||||||
public static Finder advancedFinder(String query, String orQuery, String notQuery) {
|
public static Finder advancedFinder(String query, String orQuery, String notQuery) {
|
||||||
return
|
return
|
||||||
Finder.contains(query)
|
Finder.contains(query)
|
||||||
.or(Finder.contains(orQuery))
|
.or(Finder.contains(orQuery))
|
||||||
.not(Finder.contains(notQuery));
|
.not(Finder.contains(notQuery));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtered finder looking a query with excluded queries as well.
|
// Filtered finder looking a query with excluded queries as well.
|
||||||
public static Finder filteredFinder(String query, String... excludeQueries) {
|
public static Finder filteredFinder(String query, String... excludeQueries) {
|
||||||
var finder = Finder.contains(query);
|
var finder = Finder.contains(query);
|
||||||
|
|
||||||
for (String q : excludeQueries) {
|
for (String q : excludeQueries) {
|
||||||
finder = finder.not(Finder.contains(q));
|
finder = finder.not(Finder.contains(q));
|
||||||
}
|
}
|
||||||
return finder;
|
return finder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specialized query. Every next query is looked in previous result.
|
// Specialized query. Every next query is looked in previous result.
|
||||||
public static Finder specializedFinder(String... queries) {
|
public static Finder specializedFinder(String... queries) {
|
||||||
var finder = identMult();
|
var finder = identMult();
|
||||||
|
|
||||||
for (String query : queries) {
|
for (String query : queries) {
|
||||||
finder = finder.and(Finder.contains(query));
|
finder = finder.and(Finder.contains(query));
|
||||||
}
|
}
|
||||||
return finder;
|
return finder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expanded query. Looking for alternatives.
|
// Expanded query. Looking for alternatives.
|
||||||
public static Finder expandedFinder(String... queries) {
|
public static Finder expandedFinder(String... queries) {
|
||||||
var finder = identSum();
|
var finder = identSum();
|
||||||
|
|
||||||
for (String query : queries) {
|
for (String query : queries) {
|
||||||
finder = finder.or(Finder.contains(query));
|
finder = finder.or(Finder.contains(query));
|
||||||
}
|
}
|
||||||
return finder;
|
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.
|
Now we have created the interface and methods for combinators. Now we have an application working on these combinators.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
var queriesOr = new String[]{"many", "Annabel"};
|
var queriesOr=new String[]{"many","Annabel"};
|
||||||
var finder = Finders.expandedFinder(queriesOr);
|
var finder=Finders.expandedFinder(queriesOr);
|
||||||
var res = finder.find(text());
|
var res=finder.find(text());
|
||||||
LOGGER.info("the result of expanded(or) query[{}] is {}", queriesOr, res);
|
LOGGER.info("the result of expanded(or) query[{}] is {}",queriesOr,res);
|
||||||
|
|
||||||
var queriesAnd = new String[]{"Annabel", "my"};
|
var queriesAnd=new String[]{"Annabel","my"};
|
||||||
finder = Finders.specializedFinder(queriesAnd);
|
finder=Finders.specializedFinder(queriesAnd);
|
||||||
res = finder.find(text());
|
res=finder.find(text());
|
||||||
LOGGER.info("the result of specialized(and) query[{}] is {}", queriesAnd, res);
|
LOGGER.info("the result of specialized(and) query[{}] is {}",queriesAnd,res);
|
||||||
|
|
||||||
finder = Finders.advancedFinder("it was", "kingdom", "sea");
|
finder=Finders.advancedFinder("it was","kingdom","sea");
|
||||||
res = finder.find(text());
|
res=finder.find(text());
|
||||||
LOGGER.info("the result of advanced query is {}", res);
|
LOGGER.info("the result of advanced query is {}",res);
|
||||||
|
|
||||||
res = Finders.filteredFinder(" was ", "many", "child").find(text());
|
res=Finders.filteredFinder(" was ","many","child").find(text());
|
||||||
LOGGER.info("the result of filtered query is {}", res);
|
LOGGER.info("the result of filtered query is {}",res);
|
||||||
|
|
||||||
private static String text() {
|
private static String text(){
|
||||||
return
|
return
|
||||||
"It was many and many a year ago,\n"
|
"It was many and many a year ago,\n"
|
||||||
+ "In a kingdom by the sea,\n"
|
+"In a kingdom by the sea,\n"
|
||||||
+ "That a maiden there lived whom you may know\n"
|
+"That a maiden there lived whom you may know\n"
|
||||||
+ "By the name of ANNABEL LEE;\n"
|
+"By the name of ANNABEL LEE;\n"
|
||||||
+ "And this maiden she lived with no other thought\n"
|
+"And this maiden she lived with no other thought\n"
|
||||||
+ "Than to love and be loved by me.\n"
|
+"Than to love and be loved by me.\n"
|
||||||
+ "I was a child and she was a child,\n"
|
+"I was a child and she was a child,\n"
|
||||||
+ "In this kingdom by the sea;\n"
|
+"In this kingdom by the sea;\n"
|
||||||
+ "But we loved with a love that was more than love-\n"
|
+"But we loved with a love that was more than love-\n"
|
||||||
+ "I and my Annabel Lee;\n"
|
+"I and my Annabel Lee;\n"
|
||||||
+ "With a love that the winged seraphs of heaven\n"
|
+"With a love that the winged seraphs of heaven\n"
|
||||||
+ "Coveted her and me.";
|
+"Coveted her and me.";
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Program output:**
|
**Program output:**
|
||||||
|
|
||||||
```java
|
```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 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 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 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 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
|
## Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## 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.
|
## Known Uses
|
||||||
- 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.
|
|
||||||
|
|
||||||
|
* 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
|
Benefits:
|
||||||
- java.util.function.Function#andThen
|
|
||||||
|
* 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
|
## 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)
|
- [Combinator pattern](https://wiki.haskell.org/Combinator_pattern)
|
||||||
- [Combinatory logic](https://wiki.haskell.org/Combinatory_logic)
|
- [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)
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.iluwatar.combinator;
|
package com.iluwatar.combinator;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@@ -33,12 +34,12 @@ class FinderTest {
|
|||||||
@Test
|
@Test
|
||||||
void contains() {
|
void contains() {
|
||||||
var example = """
|
var example = """
|
||||||
the first one
|
the first one
|
||||||
the second one\s
|
the second one\s
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var result = Finder.contains("second").find(example);
|
var result = Finder.contains("second").find(example);
|
||||||
assertEquals(1, result.size());
|
assertEquals(1, result.size());
|
||||||
assertEquals( "the second one ", result.get(0));
|
assertEquals("the second one ", result.get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.iluwatar.combinator;
|
package com.iluwatar.combinator;
|
||||||
|
|
||||||
import static com.iluwatar.combinator.Finders.advancedFinder;
|
import static com.iluwatar.combinator.Finders.advancedFinder;
|
||||||
@@ -38,48 +39,47 @@ class FindersTest {
|
|||||||
void advancedFinderTest() {
|
void advancedFinderTest() {
|
||||||
var res = advancedFinder("it was", "kingdom", "sea").find(text());
|
var res = advancedFinder("it was", "kingdom", "sea").find(text());
|
||||||
assertEquals(1, res.size());
|
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
|
@Test
|
||||||
void filteredFinderTest() {
|
void filteredFinderTest() {
|
||||||
var res = filteredFinder(" was ", "many", "child").find(text());
|
var res = filteredFinder(" was ", "many", "child").find(text());
|
||||||
assertEquals(1, res.size());
|
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
|
@Test
|
||||||
void specializedFinderTest() {
|
void specializedFinderTest() {
|
||||||
var res = specializedFinder("love", "heaven").find(text());
|
var res = specializedFinder("love", "heaven").find(text());
|
||||||
assertEquals(1, res.size());
|
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
|
@Test
|
||||||
void expandedFinderTest() {
|
void expandedFinderTest() {
|
||||||
var res = expandedFinder("It was", "kingdom").find(text());
|
var res = expandedFinder("It was", "kingdom").find(text());
|
||||||
assertEquals(3, res.size());
|
assertEquals(3, 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));
|
||||||
assertEquals( "In a kingdom by the sea,", res.get(1));
|
assertEquals("In a kingdom by the sea,", res.get(1));
|
||||||
assertEquals( "In this kingdom by the sea;", res.get(2));
|
assertEquals("In this kingdom by the sea;", res.get(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private String text() {
|
private String text() {
|
||||||
return
|
return """
|
||||||
"""
|
It was many and many a year ago,
|
||||||
It was many and many a year ago,
|
In a kingdom by the sea,
|
||||||
In a kingdom by the sea,
|
That a maiden there lived whom you may know
|
||||||
That a maiden there lived whom you may know
|
By the name of ANNABEL LEE;
|
||||||
By the name of ANNABEL LEE;
|
And this maiden she lived with no other thought
|
||||||
And this maiden she lived with no other thought
|
Than to love and be loved by me.
|
||||||
Than to love and be loved by me.
|
I was a child and she was a child,
|
||||||
I was a child and she was a child,
|
In this kingdom by the sea;
|
||||||
In this kingdom by the sea;
|
But we loved with a love that was more than love-
|
||||||
But we loved with a love that was more than love-
|
I and my Annabel Lee;
|
||||||
I and my Annabel Lee;
|
With a love that the winged seraphs of heaven
|
||||||
With a love that the winged seraphs of heaven
|
Coveted her and me.""";
|
||||||
Coveted her and me.""";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user