Skip to content

Commit 2228212

Browse files
authored
docs: explanation for combinator (#2877)
1 parent 30ac97f commit 2228212

File tree

3 files changed

+190
-158
lines changed

3 files changed

+190
-158
lines changed

combinator/README.md

Lines changed: 165 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,208 +1,239 @@
11
---
22
title: Combinator
3-
category: Idiom
3+
category: Functional
44
language: en
55
tag:
6-
- Reactive
6+
- Idiom
7+
- Reactive
78
---
89

910
## Also known as
1011

11-
Composition pattern
12+
* Function Composition
13+
* Functional Combinator
1214

1315
## Intent
1416

15-
The functional pattern representing a style of organizing libraries centered around the idea of combining functions.
16-
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.
17+
The Combinator pattern is intended to enable complex functionalities by combining simple functions into more complex
18+
ones. It aims to achieve modularization and reusability by breaking down a task into simpler, interchangeable components
19+
that can be composed in various ways.
1720

1821
## Explanation
1922

2023
Real world example
2124

22-
> 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.
23-
>
25+
> In computer science, combinatory logic is used as a simplified model of computation, used in computability theory and
26+
> proof theory. Despite its simplicity, combinatory logic captures many essential features of computation.
2427
2528
In plain words
2629

27-
> The combinator allows you to create new "things" from previously defined "things".
28-
>
30+
> The combinator allows you to create new "things" from previously defined "things"
2931
3032
Wikipedia says
3133

32-
> A combinator is a higher-order function that uses only function application and earlier defined combinators to define a result from its arguments.
33-
>
34+
> A combinator is a higher-order function that uses only function application and earlier defined combinators to define
35+
> a result from its arguments.
3436
3537
**Programmatic Example**
3638

37-
Translating the combinator example above. First of all, we have a interface consist of several methods `contains`, `not`, `or`, `and` .
39+
Translating the combinator example above. First of all, we have an interface consist of several
40+
methods `contains`, `not`, `or`, `and` .
3841

3942
```java
4043
// Functional interface to find lines in text.
4144
public interface Finder {
4245

43-
// The function to find lines in text.
44-
List<String> find(String text);
45-
46-
// Simple implementation of function {@link #find(String)}.
47-
static Finder contains(String word) {
48-
return txt -> Stream.of(txt.split("\n"))
49-
.filter(line -> line.toLowerCase().contains(word.toLowerCase()))
50-
.collect(Collectors.toList());
51-
}
52-
53-
// combinator not.
54-
default Finder not(Finder notFinder) {
55-
return txt -> {
56-
List<String> res = this.find(txt);
57-
res.removeAll(notFinder.find(txt));
58-
return res;
59-
};
60-
}
61-
62-
// combinator or.
63-
default Finder or(Finder orFinder) {
64-
return txt -> {
65-
List<String> res = this.find(txt);
66-
res.addAll(orFinder.find(txt));
67-
return res;
68-
};
69-
}
70-
71-
// combinator and.
72-
default Finder and(Finder andFinder) {
73-
return
74-
txt -> this
75-
.find(txt)
76-
.stream()
77-
.flatMap(line -> andFinder.find(line).stream())
78-
.collect(Collectors.toList());
79-
}
46+
// The function to find lines in text.
47+
List<String> find(String text);
48+
49+
// Simple implementation of function {@link #find(String)}.
50+
static Finder contains(String word) {
51+
return txt -> Stream.of(txt.split("\n"))
52+
.filter(line -> line.toLowerCase().contains(word.toLowerCase()))
53+
.collect(Collectors.toList());
54+
}
55+
56+
// combinator not.
57+
default Finder not(Finder notFinder) {
58+
return txt -> {
59+
List<String> res = this.find(txt);
60+
res.removeAll(notFinder.find(txt));
61+
return res;
62+
};
63+
}
64+
65+
// combinator or.
66+
default Finder or(Finder orFinder) {
67+
return txt -> {
68+
List<String> res = this.find(txt);
69+
res.addAll(orFinder.find(txt));
70+
return res;
71+
};
72+
}
73+
74+
// combinator and.
75+
default Finder and(Finder andFinder) {
76+
return
77+
txt -> this
78+
.find(txt)
79+
.stream()
80+
.flatMap(line -> andFinder.find(line).stream())
81+
.collect(Collectors.toList());
82+
}
8083
...
8184
}
8285
```
8386

84-
Then we have also another combinator for some complex finders `advancedFinder`, `filteredFinder`, `specializedFinder` and `expandedFinder`.
87+
Then we have also another combinator for some complex finders `advancedFinder`, `filteredFinder`, `specializedFinder`
88+
and `expandedFinder`.
8589

8690
```java
8791
// Complex finders consisting of simple finder.
8892
public class Finders {
8993

90-
private Finders() {
91-
}
92-
93-
// Finder to find a complex query.
94-
public static Finder advancedFinder(String query, String orQuery, String notQuery) {
95-
return
96-
Finder.contains(query)
97-
.or(Finder.contains(orQuery))
98-
.not(Finder.contains(notQuery));
99-
}
100-
101-
// Filtered finder looking a query with excluded queries as well.
102-
public static Finder filteredFinder(String query, String... excludeQueries) {
103-
var finder = Finder.contains(query);
104-
105-
for (String q : excludeQueries) {
106-
finder = finder.not(Finder.contains(q));
107-
}
108-
return finder;
109-
}
110-
111-
// Specialized query. Every next query is looked in previous result.
112-
public static Finder specializedFinder(String... queries) {
113-
var finder = identMult();
114-
115-
for (String query : queries) {
116-
finder = finder.and(Finder.contains(query));
117-
}
118-
return finder;
119-
}
120-
121-
// Expanded query. Looking for alternatives.
122-
public static Finder expandedFinder(String... queries) {
123-
var finder = identSum();
124-
125-
for (String query : queries) {
126-
finder = finder.or(Finder.contains(query));
127-
}
128-
return finder;
129-
}
94+
private Finders() {
95+
}
96+
97+
// Finder to find a complex query.
98+
public static Finder advancedFinder(String query, String orQuery, String notQuery) {
99+
return
100+
Finder.contains(query)
101+
.or(Finder.contains(orQuery))
102+
.not(Finder.contains(notQuery));
103+
}
104+
105+
// Filtered finder looking a query with excluded queries as well.
106+
public static Finder filteredFinder(String query, String... excludeQueries) {
107+
var finder = Finder.contains(query);
108+
109+
for (String q : excludeQueries) {
110+
finder = finder.not(Finder.contains(q));
111+
}
112+
return finder;
113+
}
114+
115+
// Specialized query. Every next query is looked in previous result.
116+
public static Finder specializedFinder(String... queries) {
117+
var finder = identMult();
118+
119+
for (String query : queries) {
120+
finder = finder.and(Finder.contains(query));
121+
}
122+
return finder;
123+
}
124+
125+
// Expanded query. Looking for alternatives.
126+
public static Finder expandedFinder(String... queries) {
127+
var finder = identSum();
128+
129+
for (String query : queries) {
130+
finder = finder.or(Finder.contains(query));
131+
}
132+
return finder;
133+
}
130134
...
131135
}
132136
```
133137

134138
Now we have created the interface and methods for combinators. Now we have an application working on these combinators.
135139

136140
```java
137-
var queriesOr = new String[]{"many", "Annabel"};
138-
var finder = Finders.expandedFinder(queriesOr);
139-
var res = finder.find(text());
140-
LOGGER.info("the result of expanded(or) query[{}] is {}", queriesOr, res);
141+
var queriesOr=new String[]{"many","Annabel"};
142+
var finder=Finders.expandedFinder(queriesOr);
143+
var res=finder.find(text());
144+
LOGGER.info("the result of expanded(or) query[{}] is {}",queriesOr,res);
141145

142-
var queriesAnd = new String[]{"Annabel", "my"};
143-
finder = Finders.specializedFinder(queriesAnd);
144-
res = finder.find(text());
145-
LOGGER.info("the result of specialized(and) query[{}] is {}", queriesAnd, res);
146+
var queriesAnd=new String[]{"Annabel","my"};
147+
finder=Finders.specializedFinder(queriesAnd);
148+
res=finder.find(text());
149+
LOGGER.info("the result of specialized(and) query[{}] is {}",queriesAnd,res);
146150

147-
finder = Finders.advancedFinder("it was", "kingdom", "sea");
148-
res = finder.find(text());
149-
LOGGER.info("the result of advanced query is {}", res);
151+
finder=Finders.advancedFinder("it was","kingdom","sea");
152+
res=finder.find(text());
153+
LOGGER.info("the result of advanced query is {}",res);
150154

151-
res = Finders.filteredFinder(" was ", "many", "child").find(text());
152-
LOGGER.info("the result of filtered query is {}", res);
155+
res=Finders.filteredFinder(" was ","many","child").find(text());
156+
LOGGER.info("the result of filtered query is {}",res);
153157

154-
private static String text() {
155-
return
158+
private static String text(){
159+
return
156160
"It was many and many a year ago,\n"
157-
+ "In a kingdom by the sea,\n"
158-
+ "That a maiden there lived whom you may know\n"
159-
+ "By the name of ANNABEL LEE;\n"
160-
+ "And this maiden she lived with no other thought\n"
161-
+ "Than to love and be loved by me.\n"
162-
+ "I was a child and she was a child,\n"
163-
+ "In this kingdom by the sea;\n"
164-
+ "But we loved with a love that was more than love-\n"
165-
+ "I and my Annabel Lee;\n"
166-
+ "With a love that the winged seraphs of heaven\n"
167-
+ "Coveted her and me.";
168-
}
161+
+"In a kingdom by the sea,\n"
162+
+"That a maiden there lived whom you may know\n"
163+
+"By the name of ANNABEL LEE;\n"
164+
+"And this maiden she lived with no other thought\n"
165+
+"Than to love and be loved by me.\n"
166+
+"I was a child and she was a child,\n"
167+
+"In this kingdom by the sea;\n"
168+
+"But we loved with a love that was more than love-\n"
169+
+"I and my Annabel Lee;\n"
170+
+"With a love that the winged seraphs of heaven\n"
171+
+"Coveted her and me.";
172+
}
169173
```
170174

171175
**Program output:**
172176

173177
```java
174-
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;]
175-
the result of specialized(and) query[[Annabel, my]] is [I and my Annabel Lee;]
176-
the result of advanced query is [It was many and many a year ago,]
177-
the result of filtered query is [But we loved with a love that was more than love-]
178+
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;]
179+
the result of specialized(and)query[[Annabel,my]]is[I and my Annabel Lee;]
180+
the result of advanced query is[It was many and many a year ago,]
181+
the result of filtered query is[But we loved with a love that was more than love-]
178182
```
179183

180-
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`.
181-
184+
Now we can design our app to with the queries finding
185+
feature `expandedFinder`, `specializedFinder`, `advancedFinder`, `filteredFinder` which are all derived
186+
from `contains`, `or`, `not`, `and`.
182187

183188
## Class diagram
189+
184190
![alt text](./etc/combinator.urm.png "Combinator class diagram")
185191

186192
## Applicability
187-
Use the combinator pattern when:
188193

189-
- You are able to create a more complex value from more plain values but having the same type(a combination of them)
194+
This pattern is applicable in scenarios where:
195+
196+
* The solution to a problem can be constructed from simple, reusable components.
197+
* There is a need for high modularity and reusability of functions.
198+
* The programming environment supports first-class functions and higher-order functions.
199+
200+
## Known Uses
201+
202+
* Functional programming languages like Haskell and Scala extensively use combinators for tasks ranging from parsing to
203+
UI construction.
204+
* In domain-specific languages, particularly those involved in parsing, such as parsing expression grammars.
205+
* In libraries for functional programming in languages like JavaScript, Python, and Ruby.
206+
* java.util.function.Function#compose
207+
* java.util.function.Function#andThen
208+
209+
## Consequences
210+
211+
Benefits:
190212

191-
## Benefits
213+
* Enhances modularity and reusability by breaking down complex tasks into simpler, composable functions.
214+
* Promotes readability and maintainability by using a declarative style of programming.
215+
* Facilitates lazy evaluation and potentially more efficient execution through function composition.
192216

193-
- From a developers perspective the API is made of terms from the domain.
194-
- There is a clear distinction between combining and application phase.
195-
- One first constructs an instance and then executes it.
196-
- This makes the pattern applicable in a parallel environment.
217+
Trade-offs:
197218

219+
* Can lead to a steep learning curve for those unfamiliar with functional programming principles.
220+
* May result in performance overhead due to the creation of intermediate functions.
221+
* Debugging can be challenging due to the abstract nature of function compositions.
198222

199-
## Real world examples
223+
## Related Patterns
200224

201-
- java.util.function.Function#compose
202-
- java.util.function.Function#andThen
225+
[Strategy](https://java-design-patterns.com/patterns/strategy/): Both involve selecting an algorithm at runtime, but
226+
Combinator uses composition of functions.
227+
[Decorator](https://java-design-patterns.com/patterns/decorator/): Similar to Combinator in enhancing functionality, but
228+
Decorator focuses on object augmentation.
229+
[Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/): Relies on chaining
230+
objects, whereas Combinator chains functions.
203231

204232
## Credits
205233

206-
- [Example for java](https://gtrefs.github.io/code/combinator-pattern/)
234+
- [Example for Java](https://gtrefs.github.io/code/combinator-pattern/)
207235
- [Combinator pattern](https://wiki.haskell.org/Combinator_pattern)
208236
- [Combinatory logic](https://wiki.haskell.org/Combinatory_logic)
237+
- [Structure and Interpretation of Computer Programs](https://amzn.to/3PJwVsf)
238+
- [Functional Programming in Scala](https://amzn.to/4cEo6K2)
239+
- [Haskell: The Craft of Functional Programming](https://amzn.to/4axxtcF)

combinator/src/test/java/com/iluwatar/combinator/FinderTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2323
* THE SOFTWARE.
2424
*/
25+
2526
package com.iluwatar.combinator;
2627

2728
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -33,12 +34,12 @@ class FinderTest {
3334
@Test
3435
void contains() {
3536
var example = """
36-
the first one
37-
the second one\s
38-
""";
37+
the first one
38+
the second one\s
39+
""";
3940

4041
var result = Finder.contains("second").find(example);
4142
assertEquals(1, result.size());
42-
assertEquals( "the second one ", result.get(0));
43+
assertEquals("the second one ", result.get(0));
4344
}
4445
}

0 commit comments

Comments
 (0)