Grokking Simplicity: Taming complex software with functional thinking
- Length: 550 pages
- Edition: 1
- Language: English
- Publisher: Manning Publications
- Publication Date: 2021-05-18
- ISBN-10: 1617296201
- ISBN-13: 9781617296208
- Sales Rank: #463872 (See Top 100 Books)
Grokking Simplicity is a friendly, practical guide that will change the way you approach software design and development.
Summary
Distributed across servers, difficult to test, and resistant to modification—modern software is complex. Grokking Simplicity is a friendly, practical guide that will change the way you approach software design and development. It introduces a unique approach to functional programming that explains why certain features of software are prone to complexity, and teaches you the functional techniques you can use to simplify these systems so that they’re easier to test and debug.
Purchase of the print book includes a free eBook in PDF, Kindle, and ePub formats from Manning Publications.
About the technology
Developers rightly fear the unintended complexity that infects most code. This book shows you how to write software that keeps complexity close to its inherent minimum. As you write software you should distinguish between code that alters your system’s state, and code that does not. Once you learn to make that distinction, you can refactor much of your state-altering “actions” into stateless “calculations.” Your software will be simpler.
About the book
The book also teaches you to solve the complex timing bugs that inevitably creep into asynchronous and multithreaded code. In advanced sections of the book you learn how composable abstractions help avoid repeating code and open up new levels of expressivity.
What’s inside
Patterns for simpler code
Powerful time modeling approaches to simplify asynchronous code
How higher-order functions can make code reusable and composable
About the reader
For intermediate and advanced developers building complex software. Exercises, illustrations, self-assessments, and hands-on examples lock in each new idea.
About the author
Eric Normand is an expert software developer who has been an influential teacher of functional programming since 2007.
Table of Contents
1 Welcome to Grokking Simplicity
2 Functional thinking in action
PART 1 – ACTIONS, CALCULATIONS, AND DATA
3 Distinguishing actions, calculations, and data
4 Extracting calculations from actions
5 Improving the design of actions
6 Staying immutable in a mutable language
7 Staying immutable with untrusted code
8 Stratified design, part 1
9 Stratified design, part 2
PART 2 – FIRST-CLASS ABSTRACTIONS
10 First-class functions, part 1
11 First-class functions, part 2
12 Functional iteration
13 Chaining functional tools
14 Functional tools for nested data
15 Isolating timelines
16 Sharing resources between timelines
17 Coordinating timelines
18 Reactive and onion architectures
19 The functional journey ahead
Grokking Simplicity Copyright contents front matter foreword preface acknowledgments about this book Who should read this book How this book is organized: a roadmap About the code liveBook discussion forum Other online resources about the author 1 Welcome to Grokking Simplicity What is functional programming? The problems with the definition for practical use Problem 1: FP needs side effects Problem 2: FP is good at side effects Problem 3: FP is practical The definition of FP confuses managers We treat functional programming as a set of skills and concepts Distinguishing actions, calculations, and data Functional programmers distinguish code that matters when you call it Functional programmers distinguish inert data from code that does work Functional programmers see actions, calculations, and data Step 1: The user marks a task as completed. Step 2: The client sends a message to the server. Step 3: The server receives the message. Step 4: The server makes a change to its database. Step 5: The server makes a decision of who to notify.** Step 6: The server sends an email notification. ** The three categories of code in FP 1. Actions 2. Calculations 3. Data How does distinguishing actions, calculations, and data help us? FP works great for distributed systems, and most software written today is distributed Why is this book different from other FP books? This book is practical for software engineering This book uses real-world scenarios This book focuses on software design This book conveys the richness of FP This book is language agnostic What is functional thinking? Part 1: Distinguishing actions, calculations, and data Part 2: Using first-class abstractions Ground rules for ideas and skills in this book 1. The skills can’t be based on language features 2. The skills must have immediate practical benefit 3. The skills must apply regardless of your current code situation There’s more to go, but let’s take a break for questions Conclusion Summary Up next . . . 2 Functional thinking in action Welcome to Toni’s Pizza Part 1: Distinguishing actions, calculations, and data Part 2: Using first-class abstractions Part 1: Distinguishing actions, calculations, and data 1. Actions 2. Calculations 3. Data Organizing code by “rate of change” A first glimpse of stratified design Part 2: First-class abstractions As applied to a robotic kitchen Timelines visualize distributed systems Multiple timelines can execute in different orderings Hard-won lessons about distributed systems Toni does a postmortem Cutting the timeline:Making the robots wait for each other Positive lessons learned about timelines Retrospective on coordinating robots Conclusion Summary Up next . . . Part 1 Actions, calculations, and data 3 Distinguishing actions, calculations, and data Actions, calculations, and data Actions Calculations Data 1. Thinking about a problem 2. Coding a solution 3. Reading code Actions, calculations, and data apply to any situation Check fridge Drive to store Buy what you need Drive home Lessons from our shopping process 1. We can apply the ACD perspective to any situation 2. Actions can hide actions, calculations, and data 3. Calculations can be composed of smaller calculations and data 4. Data can only be composed of more data 5. Calculations often happen “in our heads” What is data? How do we implement data? How does data encode meaning? Immutability Examples What are the advantages of data? Disadvantages There’s more to go, but let’s take a break for questions Applying functional thinking to new code A new marketing tactic at CouponDog Don’t worry about getting the wrong answer. It’s just about exploring the ideas. Drawing the coupon email process 1. Let’s start with fetching subscribers from the database 2. Fetching the coupons from the database 3. Generating the emails to send 4. Sending the emails Zooming into generating emails Implementing the coupon email process The subscriber’s data from the database The coupon rank is a string Deciding a coupon rank is a function The coupon’s data from the database The calculation to select coupons by rank is a function An email is just data The calculation to plan one email for a subscriber Planning all emails Sending the emails is an action There’s more to go, but let’s take a break for questions What are calculations? How do we implement calculations? How do calculations encode meaning? Why prefer calculations to actions? Examples of calculations What worries do calculations avoid? Disadvantage What are they typically called? Applying functional thinking to existing code Actions spread through code Actions can take many forms Function calls Method calls Constructors Expressions Statements What are actions? How are actions implemented? How do actions encode meaning? Examples What are they typically called? Actions pose a tough bargain Conclusion Summary Up next . . . 4 Extracting calculations from actions Welcome to MegaMart.com! Where your shopping cart is always full MegaMart reveals its secret code to you Calculating free shipping Your new assignment The imperative way to do it Calculating tax Your next assignment We need to make it more testable The code contains business rules that are not easy to test George from testing’s code notes George from testing’s suggestions We need to make it more reusable The accounting and shipping departments want to use our code Jenna on dev team’s code notes Jenna on dev team’s suggestions Distinguishing actions, calculations, and data Functions have inputs and outputs Inputs and outputs can be implicit or explicit Implicit inputs and outputs make a function an action Testing and reuse relate to inputs and outputs George 1: Separate business rules from DOM updates George 2: Get rid of global variables Jenna 1: Don’t depend on global variables Jenna 2: Don’t assume the answer goes in the DOM Jenna 3: Return the answer from the function Extracting a calculation from an action All of George and Jenna’s concerns are covered Extracting another calculation from an action Check if all of George and Jenna’s concerns are covered There’s more to go, but let’s take a break for questions 1. Select and extract the calculation code 2. Identify the implicit inputs and outputs of the function 3. Convert inputs to arguments and outputs to return values Let’s see all of our code in one place Conclusion Summary Up next . . . 5 Improving the design of actions Aligning design with business requirements Choosing a better level of abstraction that matches usage Aligning the function with business requirements This isn’t refactoring, really, since we’re changing the behavior There’s more to go, but let’s take a break for questions Principle: Minimize implicit inputs and outputs Reducing implicit inputs and outputs Giving the code a once-over Categorizing our calculations By grouping our calculations, we learn something about layers of meaning Principle: Design is about pulling things apart Easier to reuse Easier to maintain Easier to test Improving the design by pulling add_item() apart Extracting a copy-on-write pattern Using add_item() Categorizing our calculations There’s more to go, but let’s take a break for questions Smaller functions and more calculations Conclusion Summary Up next . . . 6 Staying immutable in a mutable language Can immutability be applied everywhere? Categorizing operations into reads, writes, or both We can categorize an operation as either a read or a write The three steps of the copy-on-write discipline Converting a write to a read with copy-on-write Complete diff from mutating to copy-on-write These copy-on-write operations are generalizable JavaScript arrays at a glance Lookup by index [idx] Set an element [] = Length .length Add to the end .push(el) Remove from the end .pop() Add to the front .unshift(el) Remove from the front .shift() Copy an array .slice() Remove items .splice(idx, num) What to do if an operation is a read and a write Splitting a function that does a read and write Splitting the operation into read and write Convert the write into a copy-on-write Returning two values from one function Wrap up the operation Convert the read and write to a read Another option 1. Split the read and write into two operations 2. Return two values There’s more to go, but let’s take a break for questions Reads to immutable data structures are calculations Reads to mutable data are actions Writes make a given piece of data mutable If there are no writes to a piece of data, it is immutable Reads to immutable data structures are calculations Converting writes to reads makes more code calculations Applications have state that changes over time Immutable data structures are fast enough We can always optimize later Garbage collectors are really fast We’re not copying as much as you might think at first Functional programming languages have fast implementations Copy-on-write operations on objects JavaScript objects at a glance Look up by key [key] Look up by key .key Set value for key .key or [key] = Remove a key/value pair delete Copy an object Object.assign(a, b) List the keys Object.keys() Converting nested writes to reads What gets copied? Visualizing shallow copies and structural sharing Conclusion Summary Up next . . . 7 Staying immutable with untrusted code Immutability with legacy code Our copy-on-write code has to interact with untrusted code Defensive copying defends the immutable original Implementing defensive copies The rules of defensive copying Rule 1: Copy as data leaves your code Rule 2: Copy as data enters your code Wrapping untrusted code Defensive copying you may be familiar with Defensive copying in web application programming interfaces (API) Defensive copying in Erlang and Elixir There’s more to go, but let’s take a break for questions Copy-on-write and defensive copying compared Copy-on-write Defensive copying Deep copies are more expensive than shallow copies Shallow copy Deep copy Implementing deep copy in JavaScript is difficult A dialogue between copy-on-write and defensive copying The topic: Which discipline is more important? Oh, brother! Moving on . . . Conclusion Summary Up next . . . 8 Stratified design: Part 1 What is software design? What is stratified design? Developing our design sense The curse of expertise The inputs to a stratified design sense The outputs from a stratified design sense Patterns of stratified design Pattern 1: Straightforward implementation Pattern 2: Abstraction barrier Pattern 3: Minimal interface Pattern 4: Comfortable layers Pattern 1: Straightforward implementations Desired shopping cart operations Checking if an item is in the cart can help us Visualizing our function calls with a call graph Straightforward implementations call functions from similar layers of abstraction There’s more to go, but let’s take a break for questions Adding remove_item_by_name() to the graph There’s more to go, but let’s take a break for questions All functions in a layer should serve the same pupose Three different zoom levels 1. Global zoom level 2. Layer zoom level 3. Function zoom level At the layer zoom level, we compare arrows across functions At the function zoom level, we compare arrows from one function Extracting the for loop There’s more to go, but let’s take a break for questions Pattern 1 Review: Straightforward implementation Straightforward code solves a problem at a single level of detail Stratified design helps us target a specific level of detail The call graph gives us a rich source of clues about levels of detail Extracting out a function makes a more general function More general functions are more reusable We don’t hide the complexity Conclusion Summary Up next . . . 9 Stratified design: Part 2 Patterns of stratified design Pattern 1: Straightforward implementation Pattern 2: Abstraction barrier Pattern 3: Minimal interface Pattern 4: Comfortable layers Pattern 2: Abstraction barrier Before abstraction barrier After abstraction barrier Abstraction barriers hide implementations Ignoring details is symmetrical Swapping the shopping cart’s data structure Re-implementing the shopping cart as an object The abstraction barrier lets us ignore details What lets us change the data structure without changing all of the code that uses shopping carts? When to use (and when not to use!) abstraction barriers 1. To facilitate changes of implementation 2. To make code easier to write and read 3. To reduce coordination between teams 4. To mentally focus on the problem at hand Pattern 2 Review: Abstraction barrier Our code is more straightforward Touching base with pattern 1: Straightforward implementation Pattern 3: Minimal interface Marketing wants to give a discount for watches Two choices for coding the marketing campaign Implementing the campaign above the barrier is better Marketing wants to log items added to the cart The design consequences of code location A better place to log adds to cart Pattern 3 Review: Minimal interface Pattern 4: Comfortable layers Patterns of stratified design Pattern 1: Straightforward implementation Pattern 2: Abstraction barrier Pattern 3: Minimal interface Pattern 4: Comfort What does the graph show us about our code? Code at the top of the graph is easier to change Testing code at the bottom is more important Code at the bottom is more reusable Summary: What the graph shows us about our code Maintainability Testability Reusability Conclusion Summary Up next . . . Part 2 10 First-class functions: Part 1 Code smell: Implicit argument in function name Refactoring: Express implicit argument Refactoring: Replace body with callback Marketing still needs to coordinate with dev Search results: Showing 2,343 tickets from marketing to dev team Code smell: Implicit argument in function name Refactoring: Express implicit argument Recognize what is and what isn’t first-class JavaScript is littered with non-first-class things, and whatever language you use is probably littered with them, too Will field names as strings lead to more bugs? Will first-class fields make the API hard to change? We will use a lot of objects and arrays First-class functions can replace any syntax For loop example: Eating and cleaning up Refactoring: Replace body with callback What is this syntax? 1. Globally defined 2. Locally defined 3. Defined inline Why are we wrapping the code in a function? Conclusion Summary Up next . . . 11 First-class functions: Part 2 One code smell and two refactorings Code smell: Implicit argument in function name Refactoring: Express implicit argument Refactoring: Replace body with callback Refactoring copy-on-write Refactoring copy-on-write for arrays 1. Identify before, body, after 2. Extract function 3. Extract callback Returning functions from functions Conclusion Summary Up next . . . 12 Functional iteration One code smell and two refactorings Code smell: Implicit argument in function name Refactoring: Express implicit argument Refactoring: Replace body with callback MegaMart is creating a communications team Code from chapter 3 Converted to forEach() Deriving map() from examples Functional tool: map() Three ways to pass a function Globally defined Locally defined Defined inline Example: Email addresses of all customers Careful, now! Deriving filter() from examples Functional tool: filter() Example: Customers with zero purchases Careful, now! Deriving reduce() from examples Functional tool: reduce() Example: Concatenating strings Careful, now! Things you can do with reduce() Undo/redo Replaying user interaction for testing Time-traveling debugger Audit trails Three functional tools compared Conclusion Summary Up next . . . 13 Chaining functional tools The customer communications team continues Clarifying chains, method 1: Name the steps Clarifying chains, method 2: Naming the callbacks Clarifying chains: Two methods compared Method 1: Naming steps Method 2: Naming callbacks Example: Emails of customers who have made one purchase Refactoring existing for loops to functional tools Strategy 1: Understand and rewrite Strategy 2: Refactor from clues Tip 1: Make data Tip 2: Operate on whole array at once Tip 3: Take many small steps Tip 3: Take many small steps Comparing functional to imperative code Summary of chaining tips Make data Operate on the whole array Many small steps Bonus: Replace conditionals with filter() Bonus: Extract helper functions Bonus: Experiment to improve Debugging tips for chaining Keep it concrete Print it out Flow your types Many other functional tools pluck() concat() frequenciesBy() and groupBy() Lodash: Functional tools for JavaScript Laravel Collections • Functional tools for PHP Clojure standard library Haskell Prelude Lambda expressions Functional interfaces Stream API reduce() for building values Getting creative with data representation Line up those dots ES6 Classic JavaScript with Lodash Java 8 Streams C# Conclusion Summary Up next . . . 14 Functional tools for nested data Higher-order functions for values in objects Making the field name explicit Deriving update() Using update() to modify values Refactoring: Replace get, modify, set with update() Steps of replace get, modify, set with update() Functional tool: update() Visualizing values in objects Visualizing nested updates Applying update() to nested data Deriving updateOption() Deriving update2() Visualizing update2() on nested objects Writing incrementSizeByName() four ways Option 1: using update() and incrementSize() Option 2: using update() and update2() Option 3: using update() Option 4: writing it manually as gets, modify, and sets Deriving update3() Deriving nestedUpdate() The anatomy of safe recursion 1. Base case 2. Recursive case 3. Progress toward the base case Visualizing nestedUpdate() The superpower of recursion Design considerations with deep nesting Abstraction barriers on deeply nested data A summary of our use of higher-order functions Replace for loops over arrays Operate effectively on nested data Apply a copy-on-write discipline Codify our try/catch logging policy Conclusion Summary Up next . . . 15 Isolating timelines There’s a bug! We can’t reproduce it when we click through slowly Now we can try to click twice fast Customers told us the bug happened when they clicked quickly We tested it a few more times and got a variety of results Let’s read the code to understand the bug The timeline diagram shows what happens over time The two fundamentals of timeline diagrams 1. If two actions occur in order, put them in the same timeline 2. If two actions can happen at the same time or out of order, they belong in separate timelines Two tricky details about the order of actions 1. ++ and += are really three steps 2. Arguments are executed before the function is called Drawing the add-to-cart timeline: Step 1 1. Identify the actions Asynchronous calls require new timelines Different languages, different threading models Single-threaded, synchronous Single-threaded, asynchronous Multi-threaded Message-passing processes Building the timeline step-by-step Drawing the add-to-cart timeline: Step 2 2. Draw each action, whether sequential or parallel Timeline diagrams capture the two kinds of sequential code Timeline diagrams capture the uncertain ordering of parallel code Principles of working with timelines 1. Fewer timelines are easier 2. Shorter timelines are easier 3. Sharing fewer resources is easier 4. Coordinate when resources are shared 5. Manipulate time as a first-class concept JavaScript’s single-thread JavaScript’s asynchronous queue What is a job? What puts jobs on the queue? What does the engine do while there are no jobs? AJAX and the event queue If it doesn’t wait for the request, how do you get the response? A complete asynchronous example Simplifying the timeline 1. Consolidate all actions on a single timeline 2. Consolidate timelines that end by creating one new timeline Reading our finished timeline Simplifying the add-to-cart timeline diagram: Step 3 1. Consolidate all actions on a single timeline 2. Consolidate timelines that end by creating one new timeline Review: Drawing the timeline (steps 1–3) Summary: Drawing timeline diagrams Identify actions Draw actions Simplify the timeline Reading timelines Timeline diagrams side-by-side can reveal problems Two slow clicks get the right result Two fast clicks can get the wrong result Timelines that share resources can cause problems We can remove problems by not sharing resources Converting a global variable to a local one The global variable total does not need to be shared 1. Identify the global variable we would like to make local 2. Replace the global variable with a local variable Converting a global variable to an argument 1. Identify the implicit input 2. Replace the implicit input with an argument Making our code more reusable Principle: In an asynchronous context, we use a final callback instead of a return value as our explicit output Conclusion Summary Up next . . . 16 Sharing resources between timelines Principles of working with timelines 1. Fewer timelines are easier 2. Shorter timelines are easier 3. Sharing fewer resources is easier 4. Coordinate when resources are shared 5. Manipulate time as a first-class concept The shopping cart still has a bug We need to guarantee the order of the DOM updates Building a queue in JavaScript JavaScript does not have a queue data structure, so we have to build one Replace the work with adding to the queue Do the work on the first item in the queue Prevent a second timeline from running at the same time as the first Modify the callback to calc_cart_total() to start the next item Stop going through items when there are no more Wrap the variables and functions in a function scope Principle: Use real-world sharing as inspiration Making the queue reusable Extracting the done() function Extracting the custom worker behavior Accepting a callback for when the task is complete Calling the callback when the task is complete It’s a higher-order function that gives an action new powers Analyzing the timeline Principle: Analyze the timeline diagram to know if there will be problems Making the queue skip Conclusion Summary Up next . . . 17 Coordinating timelines Principles of working with timelines 1. Fewer timelines are easier 2. Shorter timelines are easier 3. Sharing fewer resources is easier 4. Coordinate when resources are shared 5. Manipulate time as a first-class concept There’s a bug! How the code was changed Identify actions: Step 1 Draw each action: Step 2 Simplify the diagram: Step 3 JavaScript threading model simplification steps Possible ordering analysis Why this timeline is faster Waiting for both parallel callbacks A concurrency primitive for cutting timelines A simple example Using Cut() in our code 1. What scope to store Cut() 2. What the callback for Cut() is Uncertain ordering analysis Parallel execution analysis Multiple-click analysis A primitive to call something just once Implicit versus explicit model of time 1. Sequential statements execute in sequential order 2. Steps in two timelines can occur in two orders 3. Asynchronous events are called in new timelines 4. An action is executed as many times as you call it Summary: Manipulating timelines Reduce number of timelines Reduce length of timelines Eliminate shared resources Share resources with concurrency primitives Coordinate with concurrency primitives Conclusion Summary Up next . . . 18 Reactive and onion architectures Two separate architectural patterns Reactive architecture Onion architecture Coupling of causes and effects of changes What is reactive architecture? Tradeoffs of the reactive architecture Decouples effects from their causes Treats a series of steps as pipelines Creates flexibility in your timeline Cells are first-class state We can make ValueCells reactive We can update shipping icons when the cell changes FormulaCells calculate derived values Mutable state in functional programming How reactive architecture reconfigures systems Decouples effects from their causes Decoupling manages a center of cause and effect Treat series of steps as pipelines Flexibility in your timeline Two separate architectural patterns Reactive architecture Onion architecture What is the onion architecture? Review: Actions, calculations, and data Data Calculations Actions Review: Stratified design Traditional layered architecture A functional architecture Facilitating change and reuse Examine the terms used to place the rule in a layer Analyze readability and awkwardness Code readability Development speed System performance Conclusion Summary Up next . . . 19 The functional journey ahead A plan for the chapter Review the skills you have learned Form a model of the journey toward mastery Establish track 1: Sandbox Establish track 2: Production Continue your functional programming journey We have learned the skills of professionals Part 1: Actions, Calculations, and Data Part 2: First-class abstractions Big takeaways There are often calculations hidden in your actions Higher-order functions can reach new heights of abstraction You can control the temporal semantics of your code The ups and downs of skill acquisition Parallel tracks to mastery Sandbox: Start a side project Keep the side project small at first Make the side project whimsical Use familiar skills plus one new skill Expand the project as you will Sandbox: Practice exercises Edabit (https://edabit.com/challenges) Project Euler (https://projecteuler.net) CodeWars (https://codewars.com) Code Katas (https://github.com/gamontal/awesome-katas) Production: Eliminate a bug today Reduce the number of global mutable variables by one Reduce the number of crazy timelines by one Production: Incrementally improve the design Extract one calculation from an action Convert one implicit input or output to explicit Replace one for loop Popular functional languages Functional languages with the most jobs Functional languages by platform Browser (JavaScript engine) Web backend Mobile (iOS and Android) Embedded devices Functional languages by learning opportunity Static typing Functional tools and data transformation Concurrency and distributed systems Get mathy Lambda Calculus Combinators Type theory Category theory Effect systems Further reading Functional-Light JavaScript by Kyle Simpson Domain Modeling Made Functional by Scott Wlaschin Structure and Interpretation of Computer Programs by Harold Abelson and Gerald Jay Sussman with Julie Sussman Grokking Functional Programming by Michał Płachta Conclusion Summary Up next . . . index
Donate to keep this site alive
How to download source code?
1. Go to: https://www.manning.com
2. Search the book title: Grokking Simplicity: Taming complex software with functional thinking
, sometime you may not get the results, please search the main title
3. Click the book title in the search results
3. resources
section, click Source Code
.
1. Disable the AdBlock plugin. Otherwise, you may not get any links.
2. Solve the CAPTCHA.
3. Click download link.
4. Lead to download server to download.