Discover millions of ebooks, audiobooks, and so much more with a free trial

From $11.99/month after trial. Cancel anytime.

Composing Software: An Exploration of Functional Programming and Object Composition in JavaScript
Composing Software: An Exploration of Functional Programming and Object Composition in JavaScript
Composing Software: An Exploration of Functional Programming and Object Composition in JavaScript
Ebook413 pages3 hours

Composing Software: An Exploration of Functional Programming and Object Composition in JavaScript

Rating: 0 out of 5 stars

()

Read preview

About this ebook

This book delves into functional programming and composition techniques in JavaScript, starting with core concepts like pure functions, shared state avoidance, and higher-order functions to build modular, maintainable code. Early chapters explore the fundamentals of functional programming, immutability, and its growing influence in the JavaScript community. You'll learn essential topics such as function composition, currying, and higher-order functions, as well as advanced concepts like abstract data types, functors, and monads. The book discusses the evolution of functional programming, its role in modern software development, and addresses challenges like the software crisis and composing with classes.
You'll learn essential topics like object-oriented programming, focusing on factory functions, functional mixins, and object composition. You'll also understand why traditional classes complicate composition and discover strategies for creating custom data types, lenses, and transducers. The book also covers best practices, emphasizing clean, reusable code and avoiding anti-patterns like excessive mocking.
By the end, you'll be ready to apply functional programming techniques to tackle complex design challenges and write more maintainable JavaScript code.

LanguageEnglish
Release dateSep 30, 2024
ISBN9781836644620
Composing Software: An Exploration of Functional Programming and Object Composition in JavaScript
Author

Eric Elliott

Eric Elliott is a veteran of JavaScript application development. He is currently a member of the Creative Cloud team at Adobe. Previous roles include JavaScript Lead at Tout (social video), Senior JavaScript Rockstar at BandPage (an industry leading music app), head of client side architecture at Zumba Fitness (the leading global fitness brand), and several years as a UX and viral application consultant. He lives in the San Francisco bay area with the most beautiful woman in the world.

Related to Composing Software

Related ebooks

Software Development & Engineering For You

View More

Related articles

Reviews for Composing Software

Rating: 0 out of 5 stars
0 ratings

0 ratings0 reviews

What did you think?

Tap to rate

Review must be at least 10 words

    Book preview

    Composing Software - Eric Elliott

    Composing Software

    Composing Software

    An Exploration of Functional Programming and Object Composition in JavaScript

    Eric Elliott

    This version was published on 2024-08-11

    *   *   *   *   *

    © 2017 - 2024 Eric Elliott

    Table of Contents

    Thank You

    Composing Software: An Introduction

    You Compose Software Every Day

    Conclusion

    The Dao of Immutability (The Way of the Functional Programmer)

    Forward

    The Rise and Fall and Rise of Functional Programming (Composable Software)

    The Rise of Functional Programming

    The Fall of Functional Programming

    The Rise of Functional Programming

    Functional Programming Has Always Been Alive and Well

    Why Learn Functional Programming in JavaScript?

    Pure Functions

    What is a Function?

    Mapping

    Pure Functions

    The Trouble with Shared State

    Same Input, Same Output

    No Side Effects

    Conclusion

    What is Functional Programming?

    Pure Functions

    Function Composition

    Shared State

    Immutability

    Side Effects

    Reusability Through Higher Order Functions

    Containers, Functors, Lists, and Streams

    Declarative vs Imperative

    Conclusion

    A Functional Programmer’s Introduction to JavaScript

    Expressions and Values

    Types

    Destructuring

    Comparisons and Ternaries

    Functions

    Currying

    Function Composition

    Arrays

    Method Chaining

    Conclusion

    Higher Order Functions

    Curry and Function Composition

    What is a curried function?

    What is a partial application?

    What’s the Difference?

    What is point-free style?

    Why do we curry?

    Trace

    Curry and Function Composition, Together

    Conclusion

    Abstraction & Composition

    Abstraction is simplification.

    Abstraction in Software

    Abstraction through composition

    How to Do More with Less Code

    Conclusion

    Reduce

    Reduce is Versatile

    Conclusion

    Abstract Data Types and the Software Crisis

    Common ADT Examples

    Why ADTs?

    History of ADTs

    Specifications for ADTs

    Stack ADT Example

    Concrete Implementations

    Conclusion

    Glossary

    Functors & Categories

    Why Functors?

    Functor Laws

    Category Theory

    Build Your Own Functor

    Curried Map

    Conclusion

    Monads

    You’re probably already using monads.

    What Monads are Made of

    Building a Kleisli Composition Function

    The Monad Laws

    Conclusion

    The Forgotten History of OOP

    The Big Idea

    The Essence of OOP

    What OOP Doesn’t Mean

    What is an object?

    We’ve lost the plot.

    Object Composition

    What is Object Composition?

    Three Different Forms of Object Composition

    Notes on Code Examples

    Aggregation

    Concatenation

    Delegation

    Conclusion

    Factory Functions

    Literals for One, Factories for Many

    Returning Objects

    Destructuring

    Computed Property Keys

    Default Parameters

    Type Inference

    Factory Functions for Mixin Composition

    Conclusion

    Functional Mixins

    Motivation

    What are mixins?

    What is functional inheritance?

    What is a functional mixin?

    Composing Functional Mixins

    When to Use Functional Mixins

    Caveats

    Conclusion

    Why Composition is Harder with Classes

    The Delegate Prototype

    The .constructor Property

    Class to Factory is a Breaking Change

    Composable Custom Data Types

    You can do this with any data type

    Composable Currency

    Lenses

    Why Lenses?

    Background

    Lens Laws

    Composing Lenses

    Transducers

    Why Transducers?

    Background and Etymology

    A Musical Analogy for Transducers

    Transducers compose top-to-bottom.

    Transducer Rules

    Transducing

    The Transducer Protocol

    Conclusion

    Elements of JavaScript Style

    1. Make the function the unit of composition. One job for each function.

    2. Omit needless code.

    3. Use active voice.

    4. Avoid a succession of loose statements.

    5. Keep related code together.

    6. Put statements and expressions in positive form.

    7. Use parallel code for parallel concepts.

    Conclusion: Code should be simple, not simplistic.

    Mocking is a Code Smell

    TDD should lead to better design.

    What is a code smell?

    What is a mock?

    What is a unit test?

    What is test coverage?

    What is tight coupling?

    What causes tight coupling?

    What does composition have to do with mocking?

    How do we remove coupling?

    Code smells are warning signs, not laws. Mocks are not evil.

    Thank You

    To my editor, JS Cheerleader, who made this book much better in too many ways to list. If you find the text readable, it is because she carefully pored over every page and offered insightful feedback and encouragement every step of the way. Without her help, you would not be reading this book right now.

    To the blog readers, whose support and enthusiasm helped us turn a little blog post series into a phenomenon that attracted millions of readers and provided the momentum we needed to turn it into a book.

    To the legends of computer science who paved the way.

    If I have seen further it is by standing on the shoulders of Giants. ~ Sir Isaac Newton

    Composing Software: An Introduction

    Composition: The act of combining parts or elements to form a whole. ~ Dictionary.com

    In my first high school programming class, I was instructed that software development is the act of breaking a complex problem down into smaller problems, and composing simple solutions to form a complete solution to the complex problem.

    One of my biggest regrets in life is that I failed to understand the significance of that lesson early on. I learned the essence of software design far too late in life.

    I have interviewed hundreds of developers. What I’ve learned from those sessions is that I’m not alone. Very few working software developers have a good grasp on the essence of software development. They aren’t aware of the most important tools we have at our disposal, or how to put them to good use. 100% have struggled to answer one or both of the most important questions in the field of software development:

    What is function composition?

    What is object composition?

    The problem is that you can’t avoid composition just because you’re not aware of it. You still do it – but you do it badly. You write code with more bugs, and make it harder for other developers to understand. This is a big problem. The effects are very costly. We spend more time maintaining software than we do creating it from scratch, and our bugs impact billions of people all over the world.

    The entire world runs on software today. Every new car is a mini super-computer on wheels, and problems with software design cause real accidents and cost real human lives. In 2013, a jury found Toyota’s software development team guilty of reckless disregard after an accident investigation revealed spaghetti code with 10,000 global variables.

    Hackers and governments stockpile bugs in order to spy on people, steal credit cards, harness computing resources to launch Distributed Denial of Service (DDoS) attacks, crack passwords, and even manipulate elections.

    We must do better.

    You Compose Software Every Day

    If you’re a software developer, you compose functions and data structures every day, whether you know it or not. You can do it consciously (and better), or you can do it accidentally, with duct-tape and crazy glue.

    The process of software development is breaking down large problems into smaller problems, building components that solve those smaller problems, then composing those components together to form a complete application.

    Composing Functions

    Function composition is the process of applying a function to the output of another function. In algebra, given two functions, f and g ,

    (f \circ g)(x) = f(g(x))

    . The circle is the composition operator. It’s commonly pronounced composed with or after. You can say that out-loud as "f composed with g equals f of g of x, or f after g equals f of g of x". We say f after g because g is evaluated first, then its output is passed as an argument to f.

    Every time you write code like this, you’re composing functions:

    1 const g = n => n + 1; 2 const f = n => n * 2; 3 4 const doStuff = x => { 5   const afterG = g(x); 6   const afterF = f(afterG); 7   return afterF; 8 }; 9 10 doStuff(20); // 42

    Every time you write a promise chain, you’re composing functions:

    1 const g = n => n + 1; 2 const f = n => n * 2; 3 4 const wait = time => new Promise( 5   (resolve, reject) => setTimeout( 6     resolve, 7     time 8   ) 9 ); 10 11 wait(300) 12   .then(() => 20) 13   .then(g) 14   .then(f) 15   .then(value => console.log(value)) // 42 16 ;

    Likewise, every time you chain array method calls, lodash methods, observables (RxJS, etc.) you’re composing functions. If you’re chaining, you’re composing. If you’re passing return values into other functions, you’re composing. If you call two methods in a sequence, you’re composing using this as input data.

    If you’re chaining, you’re composing.

    When you compose functions intentionally, you’ll do it better.

    Composing functions intentionally, we can improve our doStuff() function to a simple one-liner:

    1 const g = n => n + 1; 2 const f = n => n * 2; 3 4 const doStuffBetter = x => f(g(x)); 5 6 doStuffBetter(20); // 42

    A common objection to this form is that it’s harder to debug. For example, how would we write this using function composition?

    1 const doStuff = x => { 2   const afterG = g(x); 3   console.log(`after g: ${ afterG }`); 4   const afterF = f(afterG); 5   console.log(`after f: ${ afterF }`); 6   return afterF; 7 }; 8 9 doStuff(20); // => 10 /* 11 after g: 21 12 after f: 42 13 */

    First, let’s abstract that after f, after g logging into a little utility called trace():

    1 const trace = label => value => { 2   console.log(`${ label }: ${ value }`); 3   return value; 4 };

    Now we can use it like this:

    1 const doStuff = x => { 2   const afterG = g(x); 3   trace('after g')(afterG); 4   const afterF = f(afterG); 5   trace('after f')(afterF); 6   return afterF; 7 }; 8 9 doStuff(20); // => 10 /* 11 after g: 21 12 after f: 42 13 */

    Popular functional programming libraries like Lodash and Ramda include utilities to make function composition easier. You can rewrite the above function like this:

    1 import pipe from 'lodash/fp/flow'; 2 3 const doStuffBetter = pipe( 4   g, 5   trace('after g'), 6   f, 7   trace('after f') 8 ); 9 10 doStuffBetter(20); // => 11 /* 12 after g: 21 13 after f: 42 14 */

    If you want to try this code without importing something, you can define pipe like this:

    1 // pipe(...fns: [...Function]) => x => y 2 const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

    Don’t worry if you’re not following how that works, yet. Later on we’ll explore function composition in a lot more detail. In fact, it’s so essential, you’ll see it defined and demonstrated many times throughout this text. The point is to help you become so familiar with it that its definition and usage becomes automatic. Be one with the composition.

    pipe() creates a pipeline of functions, passing the output of one function to the input of another. When you use pipe() (and its twin, compose()) You don’t need intermediary variables. Writing functions without mention of the arguments is called point-free style. To do it, you’ll call a function that returns the new function, rather than declaring the function explicitly. That means you won’t need the function keyword or the arrow syntax (=>).

    Point-free style can be taken too far, but a little bit here and there is great because those intermediary variables add unnecessary complexity to your functions.

    There are several benefits to reduced complexity:

    Working Memory

    The average human has only a few shared resources for discrete quanta in working memory, and each variable potentially consumes one of those quanta. As you add more variables, our ability to accurately recall the meaning of each variable is diminished. Working memory models typically involve 4-7 discrete quanta. Above those numbers, error rates dramatically increase.

    Using the pipe form, we eliminated 3 variables – freeing up almost half of our available working memory for other things. That reduces our cognitive load significantly. Software developers tend to be better at chunking data into working memory than the average person, but not so much more as to weaken the importance of conservation.

    Signal to Noise Ratio

    Concise code also improves the signal-to-noise ratio of your code. It’s like listening to a radio – when the radio is not tuned properly to the station, you get a lot of interfering noise, and it’s harder to hear the music. When you tune it to the correct station, the noise goes away, and you get a stronger musical signal.

    Code is the same way. More concise code expression leads to enhanced comprehension. Some code gives us useful information, and some code just takes up space. If you can reduce the amount of code you use without reducing the meaning that gets transmitted, you’ll make the code easier to parse and understand for other people who need to read it.

    Surface Area for Bugs

    Take a look at the before and after functions. It looks like the function went on a diet and lost a ton of weight. That’s important because extra code means extra surface area for bugs to hide in, which means more bugs will hide in it. Less code = less surface area for bugs = fewer bugs.

    Composing Objects

    Favor object composition over class inheritance the Gang of Four, Design Patterns: Elements of Reusable Object Oriented Software

    "In computer science, a composite data type or compound data type is any data type which can be constructed in a program using the programming language’s primitive data types and other composite types. […] The act of constructing a composite type is known as composition." ~ Wikipedia

    These are primitives:

    1 const firstName = 'Claude'; 2 const lastName = 'Debussy';

    And this is a composite:

    1 const fullName = { 2   firstName, 3   lastName 4 };

    Likewise, all Arrays, Sets, Maps, WeakMaps, TypedArrays, etc. are composite datatypes. Any time you build any non-primitive data structure, you’re performing some kind of object composition.

    Note that the Gang of Four defines a pattern called the composite pattern which is a specific type of recursive object composition which allows you to treat individual components and aggregated composites identically. Some developers get confused, thinking that the composite pattern is the only form of object composition. Don’t get confused. There are many different kinds of object composition.

    The Gang of Four continues, you’ll see object composition applied again and again in design patterns, and then they catalog three kinds of object compositional relationships, including delegation (when an object delegates property access to another object, as used in the state, strategy, and visitor patterns), acquaintance (when an object knows about another object by reference, usually passed as a parameter: a uses-a relationship, e.g., a network request handler might be passed a reference to a logger to log the request — the request handler uses a logger), and aggregation (when child objects form part of a parent object: a has-a relationship, e.g., DOM children are component elements in a DOM node — A DOM node has children).

    Class inheritance can be used to construct composite objects, but it’s a restrictive and brittle way to do it. When the Gang of Four says favor object composition over class inheritance, they’re advising you to use flexible approaches to composite object building, rather than the rigid, tightly-coupled approach of class inheritance. They’re encouraging you to favor has-a and uses-a relationships over is-a relationships.

    Rather than refer to specific design patterns, we’ll use a more general definition of object composition from Categorical Methods in Computer Science: With Aspects from Topology (1989):

    Composite objects are formed by putting objects together such that each of the latter is ‘part of’ the former.

    Another good reference is Reliable Software Through Composite Design, Glenford J Myers, 1975. Both books are long out of print, but you can still find sellers on Amazon or eBay if you’d like to explore the subject of object composition in more technical depth and historical context.

    Class inheritance is just one kind of composite object construction. All classes produce composite objects, but not all composite objects are produced by classes or class inheritance. Favor object composition over class inheritance means that you should form composite objects from small component parts, rather than inheriting all properties from an ancestor in a class hierarchy. The latter causes a large variety of well-known problems in object oriented design:

    The tight coupling problem: Because child classes are dependent on the implementation of the parent class, class inheritance is the tightest coupling available in object oriented design.

    The fragile base class problem: Due to tight coupling, changes to the base class can potentially break a large number of descendant classes – potentially in code managed by third parties. The author could break code they’re not aware of.

    The inflexible hierarchy problem: With single ancestor taxonomies, given enough time and evolution, all class taxonomies are eventually wrong for new use-cases.

    The duplication by necessity problem: Due to inflexible hierarchies, new use cases are often implemented by duplication, rather than extension, leading to similar classes which are unexpectedly divergent. Once duplication sets in, it’s not obvious which class new classes should descend from, or why.

    The gorilla/banana problem: …the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. ~ Joe Armstrong, Coders at Work

    The most common form of object composition in JavaScript is known as object concatenation (aka, concatenative inheritance: informally, mixin composition). It works like ice-cream. You start with an object (like vanilla ice-cream), and then mix in the features you want. Add some nuts, caramel, chocolate swirl, and you wind up with nutty caramel chocolate swirl ice cream.

    Building composites with class inheritance:

    1 class Foo { 2   constructor () { 3     this.a = 'a' 4   } 5 } 6 7 class Bar extends Foo { 8   constructor (options) { 9     super(options); 10     this.b = 'b' 11   } 12 } 13 14 const myBar = new Bar(); // {a: 'a', b: 'b'}

    Building composites with mixin composition:

    1 const a = { 2   a: 'a' 3 }; 4 5 const b = { 6   b: 'b' 7 }; 8 9 const c = {...a, ...b}; // {a: 'a', b: 'b'}

    We’ll explore other styles of object composition in more depth later. For now, your understanding should be:

    There’s more than one way to do it.

    Some ways are better than others.

    You want to select the simplest, most flexible solution for the task at hand.

    Conclusion

    This isn’t about functional programming (FP) vs object-oriented programming (OOP), or one language vs another. Components can take the form of functions, data structures, classes, etc. Different programming languages tend to afford different atomic elements for components. Java affords objects, Haskell affords functions, etc. But no matter what language and what paradigm you favor, you can’t get away from composing functions and data structures. In the end, that’s what it all boils down to.

    We’ll talk a lot about functional programming, because functions are the simplest things to compose in JavaScript, and the functional programming community has invested a lot of time and effort formalizing function composition techniques.

    What we won’t do is say that functional programming is better than object-oriented programming, or that you must choose one over the other. OOP vs FP is a false dichotomy. Every real Javascript application I’ve seen in recent years mixes FP and OOP extensively.

    We’ll use object composition to produce datatypes for functional programming, and functional programming to produce objects for OOP.

    No matter how you write software,

    Enjoying the preview?
    Page 1 of 1