You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: apA.md
+23-23Lines changed: 23 additions & 23 deletions
Original file line number
Diff line number
Diff line change
@@ -3,15 +3,15 @@
3
3
4
4
Transducing is a more advanced technique than we've covered in this book. It extends many of the concepts from Chapter 9 on list operations.
5
5
6
-
I wouldn't necessarily call this topic strictly "Functional-Light", but more like a bonus on top. I've left this to an appendix because you might very well need to skip the discussion for now and come back to it once you feel fairly comfortable with -- and make sure you've practiced! -- the main book concepts.
6
+
I wouldn't necessarily call this topic strictly "Functional-Light", but more like a bonus on top. I've presented this as an appendix because you might very well need to skip the discussion for now and come back to it once you feel fairly comfortable with -- and make sure you've practiced! -- the main book concepts.
7
7
8
8
To be honest, even after teaching transducing many times, and writing this chapter, I am still trying to fully wrap my brain around this technique. So don't feel bad if it twists you up. Bookmark this appendix and come back when you're ready.
9
9
10
10
Transducing means transforming with reduction.
11
11
12
12
I know that may sound like a jumble of words that confuses more than it clarifies. But let's take a look at how powerful it can be. I actually think it's one of the best illustrations of what you can do once you grasp the principles of Functional-Light Programming.
13
13
14
-
As with the rest of this book, my approach is to first explain *why*, then *how*, then finally boil it down to a simplified, repeatable *what*. That's often backwards of how many teach, but I think you'll learn the topic more deeply this way.
14
+
As with the rest of this book, my approach is to first explain *why*, then *how*, then finally boil it down to a simplified, repeatable *what*. That's often the reverse of how many teach, but I think you'll learn the topic more deeply this way.
15
15
16
16
## Why, First
17
17
@@ -42,7 +42,7 @@ It may not be obvious, but this pattern of separate adjacent list operations has
42
42
43
43
A similar performance problem arises when our arrays are async/lazy (aka Observables), processing values over time in response to events (see Chapter 10). In this scenario, only a single value comes down the event stream at a time, so processing that discrete value with two separate `filter(..)`s function calls isn't really such a big deal.
44
44
45
-
But, what's not obvious is that each `filter(..)` method produces a separate observable. The overhead of pumping a value out of one observable into another can really add up. That's especially true since in these cases, it's not uncommon for thousands or millions of values to be processed; even such small overhead costs add up quickly.
45
+
But what's not obvious is that each `filter(..)` method produces a separate observable. The overhead of pumping a value out of one observable into another can really add up. That's especially true since in these cases, it's not uncommon for thousands or millions of values to be processed; even such small overhead costs add up quickly.
46
46
47
47
The other downside is readability, especially when we need to repeat the same series of operations against multiple lists (or Observables). For example:
48
48
@@ -77,7 +77,7 @@ words
77
77
78
78
Unfortunately, combining adjacent predicate functions doesn't work as easily as combining adjacent mapping functions. To understand why, think about the "shape" of the predicate function -- a sort of academic way of describing the signature of inputs and output. It takes a single value in, and it returns a `true` or a `false`.
79
79
80
-
If you tried `isShortEnough(isLongEnough(str))`, it wouldn't work properly. `isLongEnough(..)` will return `true` / `false`, not the string value that `isShortEnough(..)` is expecting. Bummer.
80
+
If you tried `isShortEnough(isLongEnough(str))`, it wouldn't work properly. `isLongEnough(..)` will return `true`/`false`, not the string value that `isShortEnough(..)` is expecting. Bummer.
81
81
82
82
A similar frustration exists trying to compose two adjacent reducer functions. The "shape" of a reducer is a function that receives two values as input, and returns a single combined value. The output of a reducer as a single value is not suitable for input to another reducer expecting two inputs.
83
83
@@ -100,13 +100,13 @@ Hopefully these observations have illustrated why simple fusion-style compositio
100
100
101
101
## How, Next
102
102
103
-
Let's talk about how we might derive a composition of mappers, predicates and/or reducers.
103
+
Let's talk about how we might derive a composition of mappers, predicates, and/or reducers.
104
104
105
-
Don't get too overwhelmed: you won't have to go through all these mental steps we're about to explore in your own programming. Once you understand and can recognize the problem transducing solves, you'll be able just jump straight to using a `transduce(..)` utility from a FP library and move on with the rest of your application!
105
+
Don't get too overwhelmed: you won't have to go through all these mental steps we're about to explore in your own programming. Once you understand and can recognize the problem transducing solves, you'll be able to just jump straight to using a `transduce(..)` utility from a FP library and move on with the rest of your application!
106
106
107
107
Let's jump in.
108
108
109
-
### Expressing Map/Filter As Reduce
109
+
### Expressing Map/Filter as Reduce
110
110
111
111
The first trick we need to perform is expressing our `filter(..)` and `map(..)` calls as `reduce(..)` calls. Recall how we did that in Chapter 9:
112
112
@@ -159,7 +159,7 @@ function isShortEnoughReducer(list,str) {
159
159
160
160
Later, we'll revisit whether creating a new array to concatenate onto (e.g., `[...list,str]`) is necessary here or not.
161
161
162
-
### Parameterizing The Reducers
162
+
### Parameterizing the Reducers
163
163
164
164
Both filter reducers are almost identical, except they use a different predicate function. Let's parameterize that so we get one utility that can define any filter-reducer:
165
165
@@ -199,7 +199,7 @@ words
199
199
200
200
### Extracting Common Combination Logic
201
201
202
-
Look very closely at the above`mapReducer(..)` and `filterReducer(..)` functions. Do you spot the common functionality shared in each?
202
+
Look very closely at the preceding`mapReducer(..)` and `filterReducer(..)` functions. Do you spot the common functionality shared in each?
203
203
204
204
This part:
205
205
@@ -245,7 +245,7 @@ function filterReducer(predicateFn) {
245
245
246
246
Our chain still looks the same (so we won't repeat it).
247
247
248
-
### Parameterizing The Combination
248
+
### Parameterizing the Combination
249
249
250
250
Our simple `listCombination(..)` utility is only one possible way that we might combine two values. Let's parameterize the use of it to make our reducers more generalized:
251
251
@@ -304,7 +304,7 @@ But this is actually necessary to get to the next step of our derivation. Rememb
304
304
305
305
This step is the trickiest of all to visualize. So read slowly and pay close attention here.
306
306
307
-
Let's consider the curried functions from above, but without the `listCombination(..)` function having been passed in to each:
307
+
Let's consider the curried functions from earlier, but without the `listCombination(..)` function having been passed in to each:
308
308
309
309
```js
310
310
var x =curriedMapReducer( strUppercase );
@@ -466,7 +466,7 @@ words
466
466
467
467
**Note:** We should make an observation about the `compose(..)` order in the previous two snippets, which may be confusing. Recall that in our original example chain, we `map(strUppercase)` and then `filter(isLongEnough)` and finally `filter(isShortEnough)`; those operations indeed happen in that order. But in Chapter 4, we learned that `compose(..)` typically has the effect of running its functions in reverse order of listing. So why don't we need to reverse the order *here* to get the same desired outcome? The abstraction of the `combinationFn(..)` from each reducer reverses the effective applied order of operations under the hood. So counter-intuitively, when composing a tranducer, you actually want to list them in desired order of execution!
468
468
469
-
#### List Combination: Pure vs Impure
469
+
#### List Combination: Pure vs. Impure
470
470
471
471
As a quick aside, let's revisit our `listCombination(..)` combination function implementation:
472
472
@@ -489,15 +489,15 @@ function listCombination(list,val) {
489
489
490
490
Thinking about `listCombination(..)` in isolation, there's no question it's impure and that's usually something we'd want to avoid. However, there's a bigger context we should consider.
491
491
492
-
`listCombination(..)` is not a function we interact with at all. We don't directly use it anywhere in the program, instead we let the transducing process use it.
492
+
`listCombination(..)` is not a function we interact with at all. We don't directly use it anywhere in the program; instead, we let the transducing process use it.
493
493
494
494
Back in Chapter 5, we asserted that our goal with reducing side effects and defining pure functions was only that we expose pure functions to the API level of functions we'll use throughout our program. We observed that under the covers, inside a pure function, it can cheat for performance sake all it wants, as long as it doesn't violate the external contract of purity.
495
495
496
496
`listCombination(..)` is more an internal implementation detail of the transducing -- in fact, it'll often be provided by the transducing library for you! -- rather than a top-level method you'd interact with on a normal basis throughout your program.
497
497
498
498
Bottom line: I think it's perfectly acceptable, and advisable even, to use the performance-optimal impure version of `listCombination(..)`. Just make sure you document that it's impure with a code comment!
499
499
500
-
### Alternate Combination
500
+
### Alternative Combination
501
501
502
502
So far, this is what we've derived with transducing:
503
503
@@ -510,21 +510,21 @@ words
510
510
511
511
That's pretty good, but we have one final trick up our sleeve with transducing. And frankly, I think this part is what makes all this mental effort you've expended thus far, actually worth it.
512
512
513
-
Can we somehow "compose" these two `reduce(..)` calls to get it down to just one `reduce(..)`? Unfortunately, we can't just add `strConcat(..)` into the `compose(..)` call; since its a reducer and not a combination-expecting function, its shape is not correct for the composition.
513
+
Can we somehow "compose" these two `reduce(..)` calls to get it down to just one `reduce(..)`? Unfortunately, we can't just add `strConcat(..)` into the `compose(..)` call; because it's a reducer and not a combination-expecting function, its shape is not correct for the composition.
514
514
515
-
But let's look at these two functions side-by-side:
515
+
But let's look at these two functions side by side:
functionlistCombination(list,val) { list.push( val ); return list; }
521
521
```
522
522
523
-
If you squint your eyes, you can almost see how these two functions are interchangable. They operate with different data types, but conceptually they do the same thing: combine two values into one.
523
+
If you squint your eyes, you can almost see how these two functions are interchangeable. They operate with different data types, but conceptually they do the same thing: combine two values into one.
524
524
525
525
In other words, `strConcat(..)` is a combination function!
526
526
527
-
That means that we can use *it* instead of `listCombination(..)` if our end goal is to get a string concatenation rather than a list:
527
+
That means we can use *it* instead of `listCombination(..)` if our end goal is to get a string concatenation rather than a list:
528
528
529
529
```js
530
530
words.reduce( transducer( strConcat ), "" );
@@ -566,7 +566,7 @@ var transducer = compose(
566
566
);
567
567
```
568
568
569
-
`transducer(..)` still needs a combination function (like `listCombination(..)` or `strConcat(..)`) passed to it to produce a transduce-reducer function, which then can then be used (along with an initial value) in `reduce(..)`.
569
+
`transducer(..)` still needs a combination function (like `listCombination(..)` or `strConcat(..)`) passed to it to produce a transduce-reducer function, which can then be used (along with an initial value) in `reduce(..)`.
570
570
571
571
But to express all these transducing steps more declaratively, let's make a `transduce(..)` utility that does these steps for us:
572
572
@@ -593,7 +593,7 @@ transduce( transducer, strConcat, "", words );
593
593
// WRITTENSOMETHING
594
594
```
595
595
596
-
Not bad, huh!? See the `listCombination(..)` and `strConcat(..)` functions used interchangably as combination functions?
596
+
Not bad, huh!? See the `listCombination(..)` and `strConcat(..)` functions used interchangeably as combination functions?
597
597
598
598
### Transducers.js
599
599
@@ -615,13 +615,13 @@ transducers.transduce( transformer, strConcat, "", words );
615
615
616
616
Looks almost identical to above.
617
617
618
-
**Note:** The above snippet uses `transformers.comp(..)`since the library provides it, but in this case our `compose(..)` from Chapter 4 would produce the same outcome. In other words, composition itself isn't a transducing-sensitive operation.
618
+
**Note:** The preceding snippet uses `transformers.comp(..)`because the library provides it, but in this case our `compose(..)` from Chapter 4 would produce the same outcome. In other words, composition itself isn't a transducing-sensitive operation.
619
619
620
-
The composed function in this snippet is named `transformer` instead of `transducer`. That's because if we call `transformer(listCombination)` (or `transformer(strConcat)`), we won't get a straightup transduce-reducer function as earlier.
620
+
The composed function in this snippet is named `transformer` instead of `transducer`. That's because if we call `transformer(listCombination)` (or `transformer(strConcat)`), we won't get a straight-up transduce-reducer function as earlier.
621
621
622
622
`transducers.map(..)` and `transducers.filter(..)` are special helpers that adapt regular predicate or mapper functions into functions that produce a special transform object (with the transducer function wrapped underneath); the library uses these transform objects for transducing. The extra capabilities of this transform object abstraction are beyond what we'll explore, so consult the library's documentation for more information.
623
623
624
-
Since calling `transformer(..)` produces a transform object and not a typical binary transduce-reducer function, the library also provides `toFn(..)` to adapt the transform object to be useable by native array `reduce(..)`:
624
+
Because calling `transformer(..)` produces a transform object and not a typical binary transduce-reducer function, the library also provides `toFn(..)` to adapt the transform object to be useable by native array `reduce(..)`:
0 commit comments