%% generate tags start %% #software-engineering %% generate tags end %% #computer-science #software-engineering/typescript ## What is Functional Programming? Functional programming (FP) is **an approach to software development that uses pure functions to create maintainable software**. In other words, building programs by applying and composing functions. > [!info] FP is an extreme approach that avoids state and side effects, focusing on function composition. ![Dear Functional Bros (youtube.com)](https://www.youtube.com/watch?v=nuML9SmdbJ4&ab_channel=CodeAesthetic) ![The purest coding style, where bugs are near impossible (youtube.com)](https://www.youtube.com/watch?v=HlgG395PQWw&ab_channel=Coderized) - Functional programming is based on the concept of functions with no state or side effects, making it pure and elegant. - The transcript explains the transition from traditional procedural programming to functional programming, with a focus on data pipelines, filtering, and transforming data. - 🌟 Functional programming emphasizes the purity of functions with no state or side effects. - ⚙️ The transcript demonstrates how to transition from procedural to functional programming, utilizing methods like filter, map, and sort for data manipulation. - 🤔 The text reflects on the benefits and limitations of pure functional programming, highlighting the potential for a new renaissance in the future. ## The Key Concepts of Functional Programming ## What Are the Benefits of Functional Programming (FP) Compared to Object-oriented Programming (OOP) Here's a summary of the benefits of functional programming (FP) compared to object-oriented programming (OOP) in a comparison table: | Aspect | Functional Programming (FP) | Object-Oriented Programming (OOP) | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | **Immutability & State** | Emphasizes immutability, leading to fewer bugs and simpler state management. | Involves mutable objects which can lead to complex state management and unintended side effects. | | **Side Effects** | Minimizes side effects with pure functions, making code more predictable and easier to debug. | Methods can easily cause side effects, potentially leading to unpredictable system behavior. | | **Concurrency** | Natural fit for concurrent and parallel processing due to immutability and lack of side effects. | Managing concurrency can be more challenging and error-prone due to mutable state. | | **Reusability & Composition** | Functions are easily composed and reused, providing flexibility and modularity. | While classes and objects can be reused, tight coupling can make this less flexible than pure functions. | | **Testing & Modularity** | Pure functions are easy to test and modularize due to their self-contained nature. | Testing can be more complex due to dependencies and intertwined state and behaviors. | | **Code Verbosity** | Often more concise and expressive due to function composition and higher-order functions. | Can be more verbose, with more boilerplate needed for classes, objects, and inheritance. | | **Learning Curve** | Concepts like immutability and pure functions might have a steeper learning curve for those used to imperative styles. | Generally more intuitive initially, as it models real-world entities, but can become complex with deep inheritance hierarchies. | | flexibility | more flexibility | very restricted by the class and inheritance syntax and paradigm | This table encapsulates the general tendencies and benefits of each paradigm. In practice, many modern languages and frameworks blend features from both FP and OOP, allowing developers to choose the most effective techniques for their specific tasks. > [!info] you can see the example in [[Typescript constructor initialization order issue]]. Using functional programming seems to be the only way to fix the issue. ## When to Use Functional Programming and when to Use OOP? or Functional Programming is just Strictly Better than OOP? Neither functional programming (FP) nor object-oriented programming (OOP) is strictly better than the other. Each has its strengths and ideal use cases, and often the best approach depends on the specific needs of the project, the preferences and skills of the development team, and the nature of the problem being solved. Here's a guide on when to use each: ### When to Use Functional Programming: 1. **Concurrent and Parallel Systems**: FP's immutable data and pure functions make it easier to build safe concurrent systems. If your application heavily relies on concurrency and you want to avoid issues like race conditions, FP might be more suitable. 2. **Stateless Applications**: If your application is stateless or you're building functions for a stateless environment (like many serverless architectures), FP can be very effective. 3. **Complex Data Transformations**: FP is great for scenarios involving complex data transformations, pipelines, and flows, especially when the data is immutable. 4. **Mathematical Computations**: FP, with its roots in lambda calculus, is well-suited for mathematical and algorithmic problems where functions can be cleanly abstracted and composed. 5. **Testing and Debugging**: If you want to create highly testable and predictable code, FP's pure functions (which do not depend on or alter external state) can make writing tests easier. ### When to Use Object-Oriented Programming: 1. **Modeling Real-World Systems**: OOP is intuitive for modeling real-world entities and relationships using classes and objects. If your application closely mimics real-world interactions and hierarchies, OOP can be a natural fit. 2. **Large and Complex Software Systems**: For large systems where understanding the interrelation of objects is crucial, OOP can provide a clear structure through encapsulation and inheritance. 3. **GUI Applications**: Many GUI application frameworks are designed with OOP principles in mind, making OOP a preferred choice for these types of applications. 4. **Software with Complex State Management**: If your application involves managing and updating complex states, OOP can offer a more straightforward approach through objects and classes. 5. **Industry Standards and Legacy Code**: If you're working in an industry or on a project with established OOP practices or you're maintaining legacy code that's object-oriented, sticking with OOP might be more practical. 6. **Access control**: you can set public and private in class but not function. ### Combining Both Approaches: Many modern programming languages and paradigms support a mixture of FP and OOP. Here's when blending the two might be beneficial: - **Functional Core, Imperative Shell**: This approach uses FP to handle the core logic and data transformations while utilizing OOP for organizing the application and managing side effects. - **Utilizing Functional Features in OOP**: Many OOP languages now support functional features like lambda expressions, higher-order functions, and immutability. You can incorporate these features into an OOP codebase to gain some benefits of FP. ## Functional Programming Vs OOP Showcase: a case where FP is Significantly Better than OOP let's consider a simple task: filtering a list of users by age and then formatting their names. This task is common in many real-world applications, such as generating a list of members in a specific age group. ### Object-Oriented Programming (OOP) Approach: In OOP, you might create classes to represent users and the process of filtering and formatting. ```typescript // OOP Approach class User { constructor(public name: string, public age: number) {} isAdult(threshold: number): boolean { return this.age >= threshold; } formatName(): string { return `${this.name} (${this.age})`; } } class UserList { constructor(public users: User[]) {} filterAdults(threshold: number): User[] { return this.users.filter(user => user.isAdult(threshold)); } formatUserNames(): string[] { return this.users.map(user => user.formatName()); } } // Usage const users = new UserList([ new User("Alice", 22), new User("Bob", 17), new User("Charlie", 25) ]); const adults = new UserList(users.filterAdults(18)); console.log(adults.formatUserNames()); // ["Alice (22)", "Charlie (25)"] ``` ### Functional Programming (FP) Approach: In FP, you focus on pure functions and immutability. You'd use simple functions for filtering and formatting without attaching them to specific objects. ```typescript // FP Approach type User = { name: string; age: number; }; const isAdult = (user: User, threshold: number): boolean => user.age >= threshold; const formatName = (user: User): string => `${user.name} (${user.age})`; const filterAdults = (users: User[], threshold: number): User[] => users.filter(user => isAdult(user, threshold)); const formatUserNames = (users: User[]): string[] => users.map(user => formatName(user)); // Usage const users = [ { name: "Alice", age: 22 }, { name: "Bob", age: 17 }, { name: "Charlie", age: 25 } ]; const adults = filterAdults(users, 18); console.log(formatUserNames(adults)); // ["Alice (22)", "Charlie (25)"] ``` ### Comparison and Why FP Might Be Better in This Case: - ==**Simplicity and Reusability**==: The FP approach is more concise and focused. Functions like `isAdult` and `formatName` are pure and can be reused in different contexts, not just with a `User` object. - ==**Immutability**==: In FP, data is typically immutable. While not explicitly shown in this TypeScript example, FP principles discourage modifying the original user array, reducing side effects and making the code more predictable. - ==**Testability**==: Pure functions are easier to test because they depend only on their input parameters. You don't need to mock a class or its state to test a function like `isAdult` or `formatName`. - **Parallelism and Concurrency**: Pure functions can potentially be run in parallel without concern for shared state, making it easier to write safe concurrent code. This isn't demonstrated in this simple example but can be a significant advantage in more complex scenarios. In this example, the FP approach is "better" in terms of simplicity, testability, and reusability for the given task. However, it's important to note that "better" can be subjective and depends on the specific requirements and constraints of the project you're working on. In some cases, the structure and organization provided by OOP might be more advantageous, especially for more complex applications with many interacting objects and states. ## Functional Programming Vs OOP: a case where OOP is Significantly Better than FP Consider a scenario where you're building a simple drawing application that allows users to create, move, and manipulate various shapes on a canvas. This is a scenario where object-oriented programming (OOP) can shine due to its ability to closely model real-world entities and behaviors, and manage complex, mutable state. ### Object-Oriented Programming (OOP) Approach: In OOP, you can use classes to encapsulate each shape's data and behaviors, making it intuitive to manage and extend. ```typescript // OOP Approach abstract class Shape { constructor(public x: number, public y: number) {} abstract draw(): void; move(newX: number, newY: number) { this.x = newX; this.y = newY; } } class Circle extends Shape { constructor(x: number, y: number, public radius: number) { super(x, y); } draw() { console.log(`Drawing a circle at (${this.x}, ${this.y}) with radius ${this.radius}`); } } class Rectangle extends Shape { constructor(x: number, y: number, public width: number, public height: number) { super(x, y); } draw() { console.log(`Drawing a rectangle at (${this.x}, ${this.y}) with width ${this.width} and height ${this.height}`); } } // Usage const shapes: Shape[] = [ new Circle(10, 10, 5), new Rectangle(20, 20, 10, 5) ]; shapes.forEach(shape => { shape.draw(); shape.move(shape.x + 10, shape.y + 10); // Easily move the shape shape.draw(); // Draw the shape again at the new position }); ``` ### Functional Programming (FP) Approach: In FP, you would avoid mutating state and instead use pure functions to handle the shapes. ```typescript // FP Approach type Shape = Circle | Rectangle; type Circle = { kind: "circle"; x: number; y: number; radius: number; }; type Rectangle = { kind: "rectangle"; x: number; y: number; width: number; height: number; }; const drawShape = (shape: Shape) => { switch (shape.kind) { case "circle": console.log(`Drawing a circle at (${shape.x}, ${shape.y}) with radius ${shape.radius}`); break; case "rectangle": console.log(`Drawing a rectangle at (${shape.x}, ${shape.y}) with width ${shape.width} and height ${shape.height}`); break; } }; const moveShape = (shape: Shape, newX: number, newY: number): Shape => { switch (shape.kind) { case "circle": return { ...shape, x: newX, y: newY }; case "rectangle": return { ...shape, x: newX, y: newY }; } }; // Usage let shapes: Shape[] = [ { kind: "circle", x: 10, y: 10, radius: 5 }, { kind: "rectangle", x: 20, y: 20, width: 10, height: 5 } ]; shapes.forEach(shape => { drawShape(shape); shape = moveShape(shape, shape.x + 10, shape.y + 10); // Need to reassign the moved shape drawShape(shape); }); ``` ### Comparison and Why OOP Might Be Better in This Case: - **Intuitive Modeling**: OOP's ability to model real-world entities (like shapes in a drawing app) makes the code more intuitive and aligned with the problem domain. Each shape's data and behavior are encapsulated in a class. - ==**State Management**==: In applications where objects have mutable state that changes frequently (like moving shapes around), OOP provides a straightforward way to manage and update this state. - **Extensibility**: ==OOP makes it easier to extend the system with new types of shapes==. You can add a new class for each shape, and it will work seamlessly with the existing infrastructure. - **Code Organization**: OOP provides clear organization and structure, especially beneficial in larger, more complex applications where maintaining clear relationships between different entities is crucial. In this scenario, OOP provides significant advantages in terms of intuitiveness, ease of state management, and extensibility. While it's possible to manage this in a functional style, the mutable nature of the shapes and the frequent changes to their state make OOP a more natural fit for this particular problem.