Composing Software: An Exploration of Functional Programming and Object Composition in JavaScript
By Eric Elliott
()
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.
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
Tiny CSS Projects Rating: 0 out of 5 stars0 ratingsNGINX HTTP Server: Harness the power of NGINX with a series of detailed tutorials and real-life examples Rating: 0 out of 5 stars0 ratingsOne Sunday at a Time (Cycle C): Preparing Your Heart for Weekly Mass Rating: 0 out of 5 stars0 ratingsMan to Man, Dad to Dad: Catholic Faith and Fatherhood Rating: 0 out of 5 stars0 ratingsCode the Classics Volume II Rating: 0 out of 5 stars0 ratingsReact and React Native: Build cross-platform JavaScript and TypeScript apps for the web, desktop, and mobile Rating: 0 out of 5 stars0 ratingsIvor Horton's Beginning Visual C++ 2013 Rating: 0 out of 5 stars0 ratingsHigh Performance SQL Server: Consistent Response for Mission-Critical Applications Rating: 0 out of 5 stars0 ratingsProgramming ADO.NET Rating: 0 out of 5 stars0 ratingsPro SQL Server on Linux: Including Container-Based Deployment with Docker and Kubernetes Rating: 0 out of 5 stars0 ratingsPractical HTML and CSS: Elevate your internet presence by creating modern and high-performance websites for the web Rating: 0 out of 5 stars0 ratingsAngular Cookbook: Over 80 actionable recipes every Angular developer should know Rating: 0 out of 5 stars0 ratingsGood Habits for Great Coding: Improving Programming Skills with Examples in Python Rating: 0 out of 5 stars0 ratingsAutoIT Scripting For Beginners Rating: 5 out of 5 stars5/5Learn to Program with Kotlin: From the Basics to Projects with Text and Image Processing Rating: 0 out of 5 stars0 ratingsGrokking Simplicity: Taming complex software with functional thinking Rating: 4 out of 5 stars4/5Tapestry 5: Building Web Applications Rating: 3 out of 5 stars3/5Easy Programming for Everyone Rating: 0 out of 5 stars0 ratingsC in 30 Pages Rating: 5 out of 5 stars5/5The 1 Page Python Book Rating: 2 out of 5 stars2/5Test-Driven iOS Development with Swift 3 Rating: 0 out of 5 stars0 ratingsSoftware Engineering from Scratch: A Comprehensive Introduction Using Scala Rating: 0 out of 5 stars0 ratingsThinking About Star Rating: 0 out of 5 stars0 ratingsLearn R By Coding Rating: 0 out of 5 stars0 ratingsTest-Driven iOS Development with Swift 4 - Third Edition: Write Swift code that is maintainable, flexible, and easily extensible Rating: 0 out of 5 stars0 ratingsCrystal Clear Java: 1St Edition Rating: 0 out of 5 stars0 ratingsC# Programming Illustrated Guide For Beginners & Intermediates: The Future Is Here! Learning By Doing Approach Rating: 0 out of 5 stars0 ratingsDataflow and Reactive Programming Systems Rating: 0 out of 5 stars0 ratings
Software Development & Engineering For You
Grokking Algorithms: An illustrated guide for programmers and other curious people Rating: 4 out of 5 stars4/5Learn to Code. Get a Job. The Ultimate Guide to Learning and Getting Hired as a Developer. Rating: 5 out of 5 stars5/5Coding with AI For Dummies Rating: 1 out of 5 stars1/5Hand Lettering on the iPad with Procreate: Ideas and Lessons for Modern and Vintage Lettering Rating: 4 out of 5 stars4/5Python For Dummies Rating: 4 out of 5 stars4/5Beginning Programming For Dummies Rating: 4 out of 5 stars4/5Managing Humans: Biting and Humorous Tales of a Software Engineering Manager Rating: 4 out of 5 stars4/5SQL For Dummies Rating: 0 out of 5 stars0 ratingsPython Handbook For Beginners. A Hands-On Crash Course For Kids, Newbies and Everybody Else Rating: 0 out of 5 stars0 ratingsCoding All-in-One For Dummies Rating: 0 out of 5 stars0 ratingsKodi Made Easy: Complete Beginners Step by Step Guide on How to Install Kodi on Amazon Firestick Rating: 0 out of 5 stars0 ratingsPYTHON: Practical Python Programming For Beginners & Experts With Hands-on Project Rating: 5 out of 5 stars5/5Kanban in Action Rating: 0 out of 5 stars0 ratingsMaking Money By Selling 3D Models Online Rating: 5 out of 5 stars5/5Agile Practice Guide Rating: 4 out of 5 stars4/5Ry's Git Tutorial Rating: 0 out of 5 stars0 ratingsOneNote: The Ultimate Guide on How to Use Microsoft OneNote for Getting Things Done Rating: 1 out of 5 stars1/5Gray Hat Hacking the Ethical Hacker's Rating: 5 out of 5 stars5/5Dependency Injection Principles, Practices, and Patterns Rating: 5 out of 5 stars5/5iOS App Development For Dummies Rating: 0 out of 5 stars0 ratingsLearning Python Rating: 5 out of 5 stars5/5Test-Driven iOS Development with Swift Rating: 5 out of 5 stars5/5Photoshop For Beginners: Learn Adobe Photoshop cs5 Basics With Tutorials Rating: 0 out of 5 stars0 ratingsLevel Up! The Guide to Great Video Game Design Rating: 4 out of 5 stars4/5Blender 3D Printing Essentials Rating: 0 out of 5 stars0 ratingsAndroid App Development For Dummies Rating: 0 out of 5 stars0 ratings
Reviews for Composing Software
0 ratings0 reviews
Book preview
Composing Software - Eric Elliott
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,