diff options
Diffstat (limited to 'bash/talk-to-computer/corpus/programming/functional_programming.md')
-rw-r--r-- | bash/talk-to-computer/corpus/programming/functional_programming.md | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/bash/talk-to-computer/corpus/programming/functional_programming.md b/bash/talk-to-computer/corpus/programming/functional_programming.md new file mode 100644 index 0000000..2572442 --- /dev/null +++ b/bash/talk-to-computer/corpus/programming/functional_programming.md @@ -0,0 +1,234 @@ +# Functional Programming - A paradigm for declarative, predictable code + +## Introduction + +**Functional Programming (FP)** is a programming paradigm where software is built by composing **pure functions**, avoiding shared state, mutable data, and side-effects. It treats computation as the evaluation of mathematical functions. Instead of describing *how* to achieve a result (imperative programming), you describe *what* the result is (declarative programming). + +This paradigm has gained significant traction because it helps manage the complexity of modern applications, especially those involving concurrency and complex state management. Programs written in a functional style are often easier to reason about, test, and debug. + +## Core Concepts + +### Pure Functions + +A function is **pure** if it adheres to two rules: + +1. **The same input always returns the same output.** The function's return value depends solely on its input arguments. +2. **It produces no side effects.** A side effect is any interaction with the "outside world" from within the function. This includes modifying a global variable, changing an argument, logging to the console, or making a network request. + +<!-- end list --> + +```javascript +// Pure function: predictable and testable +const add = (a, b) => a + b; +add(2, 3); // Always returns 5 + +// Impure function: has a side effect (console.log) +let count = 0; +const incrementWithLog = () => { + count++; // And mutates external state + console.log(`The count is ${count}`); + return count; +}; +``` + +### Immutability + +**Immutability** means that data, once created, cannot be changed. If you need to modify a data structure (like an object or array), you create a new one with the updated values instead of altering the original. This prevents bugs caused by different parts of your application unexpectedly changing the same piece of data. + +```javascript +// Bad: Mutating an object +const user = { name: "Alice", age: 30 }; +const celebrateBirthdayMutable = (person) => { + person.age++; // This modifies the original user object + return person; +}; + +// Good: Returning a new object +const celebrateBirthdayImmutable = (person) => { + return { ...person, age: person.age + 1 }; // Creates a new object +}; + +const newUser = celebrateBirthdayImmutable(user); +// user is still { name: "Alice", age: 30 } +// newUser is { name: "Alice", age: 31 } +``` + +### First-Class and Higher-Order Functions + +In FP, functions are **first-class citizens**. This means they can be treated like any other value: + + * Assigned to variables + * Stored in data structures + * Passed as arguments to other functions + * Returned as values from other functions + +A function that either takes another function as an argument or returns a function is called a **Higher-Order Function**. Common examples are `map`, `filter`, and `reduce`. + +```javascript +const numbers = [1, 2, 3, 4]; +const isEven = (n) => n % 2 === 0; +const double = (n) => n * 2; + +// `filter` and `map` are Higher-Order Functions +const evenDoubled = numbers.filter(isEven).map(double); // [4, 8] +``` + +## Key Principles + + - **Declarative Style**: Focus on *what* the program should accomplish, not *how* it should accomplish it. An SQL query is a great example of a declarative style. + - **No Side Effects**: Isolate side effects from the core logic of your application. This makes your code more predictable. + - **Function Composition**: Build complex functionality by combining small, reusable functions. + - **Referential Transparency**: An expression can be replaced with its value without changing the behavior of the program. This is a natural outcome of using pure functions and immutable data. + +## Implementation/Usage + +The core idea is to create data transformation pipelines. You start with initial data and pass it through a series of functions to produce the final result. + +### Basic Example + +```javascript +// A simple pipeline for processing a list of users +const users = [ + { name: "Alice", active: true, score: 90 }, + { name: "Bob", active: false, score: 80 }, + { name: "Charlie", active: true, score: 95 }, +]; + +/** + * @param {object[]} users + * @returns {string[]} + */ +const getHighScoringActiveUserNames = (users) => { + return users + .filter((user) => user.active) + .filter((user) => user.score > 85) + .map((user) => user.name.toUpperCase()); +}; + +console.log(getHighScoringActiveUserNames(users)); // ["ALICE", "CHARLIE"] +``` + +### Advanced Example + +A common advanced pattern is to use a reducer function to manage application state, a core concept in The Elm Architecture and libraries like Redux. + +```javascript +// The state of our simple counter application +const initialState = { count: 0 }; + +// A pure function that describes how state changes in response to an action +const counterReducer = (state, action) => { + switch (action.type) { + case 'INCREMENT': + return { ...state, count: state.count + 1 }; + case 'DECREMENT': + return { ...state, count: state.count - 1 }; + case 'RESET': + return { ...state, count: 0 }; + default: + return state; + } +}; + +// Simulate dispatching actions +let state = initialState; +state = counterReducer(state, { type: 'INCREMENT' }); // { count: 1 } +state = counterReducer(state, { type: 'INCREMENT' }); // { count: 2 } +state = counterReducer(state, { type: 'DECREMENT' }); // { count: 1 } + +console.log(state); // { count: 1 } +``` + +## Common Patterns + +### Pattern 1: Functor + +A **Functor** is a design pattern for a data structure that can be "mapped over." It's a container that holds a value and has a `map` method for applying a function to that value without changing the container's structure. The most common example is the `Array`. + +```javascript +// Array is a Functor because it has a .map() method +const numbers = [1, 2, 3]; +const addOne = (n) => n + 1; +const result = numbers.map(addOne); // [2, 3, 4] +``` + +### Pattern 2: Monad + +A **Monad** is a pattern for sequencing computations. Think of it as a "safer" functor that knows how to handle nested contexts or operations that can fail (like Promises or the `Maybe` type). `Promise` is a good practical example; its `.then()` method (or `flatMap`) lets you chain asynchronous operations together seamlessly. + +```javascript +// Promise is a Monad, allowing chaining of async operations +const fetchUser = (id) => Promise.resolve({ id, name: "Alice" }); +const fetchUserPosts = (user) => Promise.resolve([ { userId: user.id, title: "Post 1" } ]); + +fetchUser(1) + .then(fetchUserPosts) // .then acts like flatMap here + .then(posts => console.log(posts)) + .catch(err => console.error(err)); +``` + +## Best Practices + + - **Keep Functions Small**: Each function should do one thing well. + - **Use Function Composition**: Use utilities like `pipe` or `compose` to build complex logic from simple building blocks. + - **Embrace Immutability**: Use `const` by default. Avoid reassigning variables. When updating objects or arrays, create new ones. + - **Isolate Impurity**: Side effects are necessary. Keep them at the boundaries of your application (e.g., in the function that handles an API call) and keep your core business logic pure. + +## Common Pitfalls + + - **Accidental Mutation**: JavaScript objects and arrays are passed by reference, making it easy to mutate them accidentally. Be vigilant, especially with nested data. + - **Over-Abstraction**: Don't use complex FP concepts like monad transformers if a simple function will do. Prioritize readability. + - **Performance Misconceptions**: While creating many short-lived objects can have a performance cost, modern JavaScript engines are highly optimized for this pattern. Don't prematurely optimize; measure first. + +## Performance Considerations + + - **Object/Array Creation**: In performance-critical code (e.g., animations, large data processing), the overhead of creating new objects/arrays in a tight loop can be significant. + - **Structural Sharing**: Libraries like `Immer` and `Immutable.js` use a technique called structural sharing. When you "change" an immutable data structure, only the parts that changed are created anew; the rest of the structure points to the same old data, saving memory and CPU time. + - **Recursion**: Deep recursion can lead to stack overflow errors. While some languages support **Tail Call Optimization (TCO)** to prevent this, JavaScript engines have limited support. Prefer iteration for very large data sets. + +## Integration Points + + - **UI Frameworks**: FP concepts are central to modern UI libraries. **React** encourages pure components and uses immutable state patterns with Hooks (`useState`, `useReducer`). + \-- **State Management**: Libraries like **Redux** and **Zustand** are built entirely on FP principles, particularly the use of pure reducer functions. + - **Data Processing**: FP is excellent for data transformation pipelines. It's often used in backend services for processing streams of data. + - **Utility Libraries**: Libraries like **Lodash/fp** and **Ramda** provide a rich toolkit of pre-built, curried, and pure functions for everyday tasks. + +## Troubleshooting + +### Problem 1: Debugging composed function pipelines + +**Symptoms:** A chain of `.map().filter().reduce()` produces an incorrect result, and it's hard to see where it went wrong. +**Solution:** Break the chain apart. Log the intermediate result after each step to inspect the data as it flows through the pipeline. + +```javascript +const result = users + .filter((user) => user.active) + // console.log('After active filter:', resultFromActiveFilter) + .filter((user) => user.score > 85) + // console.log('After score filter:', resultFromScoreFilter) + .map((user) => user.name.toUpperCase()); +``` + +### Problem 2: State changes unexpectedly + +**Symptoms:** A piece of state (e.g., in a React component or Redux store) changes when it shouldn't have, leading to bugs or infinite re-renders. +**Solution:** This is almost always due to accidental mutation. Audit your code to ensure you are not modifying state directly. Use the spread syntax (`...`) for objects and arrays (`[...arr, newItem]`) to create copies. Libraries like `Immer` can make this process safer and more concise. + +## Examples in Context + + - **Frontend Web Development**: The **Elm Architecture** (Model, Update, View) is a purely functional pattern for building web apps. It has heavily influenced libraries like Redux. + - **Data Analysis**: Running a series of transformations on a large dataset to filter, shape, and aggregate it for a report. + - **Concurrency**: Handling multiple events or requests simultaneously without running into race conditions, because data is immutable and shared state is avoided. + +## References + + - [MDN Web Docs: Functional Programming](https://www.google.com/search?q=https://developer.mozilla.org/en-US/docs/Glossary/Functional_programming) + - [Professor Frisby's Mostly Adequate Guide to Functional Programming](https://mostly-adequate.gitbook.io/mostly-adequate-guide/) + - [Ramda Documentation](https://ramdajs.com/docs/) + +## Related Topics + + - Immutability + - Functional Reactive Programming (FRP) + - The Elm Architecture + - Algebraic Data Types \ No newline at end of file |