about summary refs log tree commit diff stats
path: root/bash/talk-to-computer/corpus/programming/combinators.md
diff options
context:
space:
mode:
Diffstat (limited to 'bash/talk-to-computer/corpus/programming/combinators.md')
-rw-r--r--bash/talk-to-computer/corpus/programming/combinators.md192
1 files changed, 192 insertions, 0 deletions
diff --git a/bash/talk-to-computer/corpus/programming/combinators.md b/bash/talk-to-computer/corpus/programming/combinators.md
new file mode 100644
index 0000000..8e2cfb0
--- /dev/null
+++ b/bash/talk-to-computer/corpus/programming/combinators.md
@@ -0,0 +1,192 @@
+# Combinators - The Ultimate Reusable Functions
+
+## Introduction
+
+In the context of functional programming and computer science, a **combinator** is a higher-order function that uses only function application and other combinators to define a result. Crucially, a combinator contains **no free variables**. This means it is a completely self-contained function that only refers to its own arguments.
+
+Combinators are fundamental concepts from **combinatory logic** and **lambda calculus**. While they have deep theoretical importance, their practical application in software development is to create highly reusable, abstract, and composable code, often leading to a **point-free** or **tacit** programming style. They are the essential glue for building complex logic by piecing together simpler functions.
+
+## Core Concepts
+
+### No Free Variables
+
+The defining characteristic of a combinator is that it has no **free variables**. A free variable is a variable referenced in a function that is not one of its formal arguments or defined within the function's local scope. This self-contained nature makes combinators perfectly portable and predictable.
+
+```javascript
+const y = 10;
+
+// This function is NOT a combinator because it uses a free variable `y`.
+// Its behavior depends on an external context.
+const addY = (x) => x + y;
+
+// This function IS a combinator. It has no free variables.
+// Its behavior only depends on its arguments.
+const add = (x) => (z) => x + z;
+```
+
+### Function Composition and Transformation
+
+Combinators are designed to manipulate and combine other functions. They are the building blocks for creating new functions from existing ones without needing to specify the data that the functions will eventually operate on. The entire logic is expressed as a transformation of functions themselves.
+
+## Key Principles
+
+  - **Point-Free Style (Tacit Programming)**: This is the primary programming style associated with combinators. You define functions as a pipeline or composition of other functions without explicitly mentioning the arguments (the "points"). This can lead to more abstract and declarative code.
+
+    ```javascript
+    // Not point-free: the argument `users` is explicitly mentioned.
+    const getActiveUserNames = (users) => users.filter(user => user.active).map(user => user.name);
+
+    // Point-free style: built by composing functions.
+    // `compose`, `filter`, `map`, and `prop` are all combinators or higher-order functions.
+    const getActiveUserNamesPointFree = compose(map(prop('name')), filter(propEq('active', true)));
+    ```
+
+  - **Abstraction**: Combinators abstract common patterns of execution and control flow. For example, the act of applying one function's result to another is abstracted away by the `compose` combinator.
+
+## Implementation/Usage
+
+Many famous combinators have single-letter names from combinatory logic. Understanding them helps in recognizing fundamental patterns.
+
+### Basic Example
+
+The simplest combinators are the **I-combinator (Identity)** and the **K-combinator (Constant)**.
+
+```javascript
+/**
+ * I-combinator (Identity)
+ * Takes a value and returns it.
+ * I x = x
+ */
+const I = (x) => x;
+
+/**
+ * K-combinator (Constant or Kestrel)
+ * Takes two arguments and returns the first. Creates constant functions.
+ * K x y = x
+ */
+const K = (x) => (y) => x;
+
+// Usage:
+const value = I("hello"); // "hello"
+const always42 = K(42);
+const result = always42("some other value"); // 42
+```
+
+### Advanced Example
+
+More complex combinators handle function composition, like the **B-combinator (Bluebird)**.
+
+```javascript
+/**
+ * B-combinator (Bluebird / Function Composition)
+ * Composes two functions.
+ * B f g x = f (g x)
+ */
+const B = (f) => (g) => (x) => f(g(x));
+
+// In practice, this is often implemented as `compose`.
+const compose = (f, g) => (x) => f(g(x));
+
+// Usage:
+const double = (n) => n * 2;
+const increment = (n) => n + 1;
+
+// Create a new function that increments then doubles.
+const incrementThenDouble = compose(double, increment);
+
+incrementThenDouble(5); // Returns 12, because (5 + 1) * 2
+```
+
+Another useful combinator is the **T-combinator (Thrush)**, which applies a value to a function.
+
+```javascript
+/**
+ * T-combinator (Thrush)
+ * Takes a value and a function, and applies the function to the value.
+ * T x f = f x
+ */
+const T = (x) => (f) => f(x);
+
+// This is the basis for the `pipe` or "thread-first" operator.
+T(5, increment); // 6
+```
+
+## Common Patterns
+
+### Pattern 1: Function Composition (`compose` / `pipe`)
+
+This is the most common and practical application of combinators. `compose` (based on the B-combinator) applies functions from right to left, while `pipe` applies them from left to right. They are used to build data-processing pipelines in a point-free style.
+
+```javascript
+// Ramda-style compose, handles multiple functions
+const compose = (...fns) => (initialVal) => fns.reduceRight((val, fn) => fn(val), initialVal);
+const pipe = (...fns) => (initialVal) => fns.reduce((val, fn) => fn(val), initialVal);
+```
+
+### Pattern 2: Parser Combinators
+
+A parser combinator is a higher-order function that takes several parsers as input and returns a new parser as its output. This is an advanced technique for building complex parsers by combining simple, specialized parsers for different parts of a grammar. It's a powerful real-world application of combinator logic.
+
+## Best Practices
+
+  - **Prioritize Readability**: While point-free style can be elegant, it can also become cryptic. If a composition is too long or complex, break it down and give intermediate functions meaningful names.
+  - **Know Your Library**: If you are using a functional programming library like Ramda or fp-ts, invest time in learning the combinators it provides. They are the building blocks for effective use of the library.
+  - **Use Currying**: Combinators are most powerful in a language that supports currying, as it allows for partial application, creating specialized functions from general ones.
+
+## Common Pitfalls
+
+  - **"Pointless" Code**: Overuse of point-free style can lead to code that is very difficult to read and debug. The goal is clarity through abstraction, not just character count reduction.
+  - **Debugging Complexity**: Debugging a long chain of composed functions is challenging because there are no named intermediate values to inspect. You often have to break the chain apart to find the source of a bug.
+
+## Performance Considerations
+
+  - **Function Call Overhead**: In theory, a deeply nested composition of combinators can introduce a small overhead from the additional function calls.
+  - **Negligible in Practice**: In most real-world applications, this overhead is negligible and completely optimized away by modern JavaScript engines and language compilers. Code clarity and correctness are far more important concerns.
+
+## Integration Points
+
+  - **Functional Programming Libraries**: Libraries like **Ramda**, **Lodash/fp**, and the **Haskell Prelude** are essentially collections of combinators and other higher-order functions.
+  - **Lambda Calculus**: Combinatory logic, the formal study of combinators, is computationally equivalent to lambda calculus. The famous **SKI combinator calculus** (using only S, K, and I combinators) can be used to express any computable algorithm.
+  - **Parser Combinator Libraries**: Libraries like `parsec` in Haskell or `fast-check` in JavaScript use these principles to build robust parsers and property-based testing tools.
+
+## Troubleshooting
+
+### Problem 1: A Composed Function Behaves Incorrectly
+
+**Symptoms:** The final output of a point-free pipeline is `undefined`, `NaN`, or simply the wrong value.
+**Solution:** Temporarily "re-point" the function to debug. Break the composition and insert `console.log` statements (or a `tap` utility function) to inspect the data as it flows from one function to the next.
+
+```javascript
+// A "tap" combinator is useful for debugging.
+const tap = (fn) => (x) => {
+  fn(x);
+  return x;
+};
+
+// Insert it into a pipeline to inspect intermediate values.
+const problematicPipe = pipe(
+  increment,
+  tap(console.log), // See the value after incrementing
+  double
+);
+```
+
+## Examples in Context
+
+  - **Configuration Objects**: Using the K-combinator (constant function) to provide default configuration values.
+  - **Data Validation**: Building a validator by composing smaller validation rule functions, where each function takes data and returns either a success or failure indicator.
+  - **Web Development**: A point-free pipeline in a frontend application that takes a raw API response, filters out inactive items, extracts a specific field, and formats it for display.
+
+## References
+
+  - [To Mock a Mockingbird by Raymond Smullyan](https://en.wikipedia.org/wiki/To_Mock_a_Mockingbird) - An accessible and famous book that teaches combinatory logic through recreational puzzles.
+  - [Wikipedia: Combinatory Logic](https://en.wikipedia.org/wiki/Combinatory_logic)
+  - [Ramda Documentation](https://ramdajs.com/docs/)
+
+## Related Topics
+
+  - Point-Free Style
+  - Lambda Calculus
+  - Functional Programming
+  - Currying
+  - Higher-Order Functions
\ No newline at end of file