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
+26-22Lines changed: 26 additions & 22 deletions
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
# Functional-Light JavaScript
2
2
# Appendix A: Transducing
3
3
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.
4
+
Transducing is a more advanced technique than we've covered in this book. It extends many of the concepts from [Chapter 9](ch9.md) on list operations.
5
5
6
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
@@ -15,7 +15,7 @@ As with the rest of this book, my approach is to first explain *why*, then *how*
15
15
16
16
## Why, First
17
17
18
-
Let's start by extending a scenario we covered back in Chapter 3, testing words to see if they're short enough and/or long enough:
18
+
Let's start by extending a [scenario we covered back in Chapter 3](ch3.md/#user-content-shortlongenough), testing words to see if they're short enough and/or long enough:
19
19
20
20
```js
21
21
functionisLongEnough(str) {
@@ -40,7 +40,7 @@ words
40
40
41
41
It may not be obvious, but this pattern of separate adjacent list operations has some non-ideal characteristics. When we're dealing with only a single array of a small number of values, everything is fine. But if there were lots of values in the array, each `filter(..)` processing the list separately can slow down a bit more than we'd like.
42
42
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.
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](ch10.md)). 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
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
@@ -66,7 +66,7 @@ function isCorrectLength(str) {
66
66
67
67
But that's not the FP way!
68
68
69
-
In Chapter 9, we talked about fusion -- composing adjacent mapping functions. Recall:
69
+
In [Chapter 9, we talked about fusion](ch9.md/#fusion) -- composing adjacent mapping functions. Recall:
70
70
71
71
```js
72
72
words
@@ -108,7 +108,7 @@ Let's jump in.
108
108
109
109
### Expressing Map/Filter as Reduce
110
110
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:
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](ch9.md/#map-as-reduce):
That's a decent improvement. We now have four adjacent `reduce(..)` calls instead of a mixture of three different methods all with different shapes. We still can't just `compose(..)` those four reducers, however, because they accept two arguments instead of one.
141
141
142
-
In Chapter 9, we sort of cheated and used `list.push(..)` to mutate as a side effect rather than creating a whole new array to concatenate onto. Let's step back and be a bit more formal for now:
142
+
<aname="cheating"></a>
143
+
144
+
In [Chapter 9, we sort of cheated](ch9.md/#user-content-reducecheating) and used `list.push(..)` to mutate as a side effect rather than creating a whole new array to concatenate onto. Let's step back and be a bit more formal for now:
143
145
144
146
```js
145
147
functionstrUppercaseReducer(list,str) {
@@ -281,12 +283,13 @@ var curriedMapReducer = curry( function mapReducer(mapperFn,combinationFn){
281
283
};
282
284
} );
283
285
284
-
var curriedFilterReducer =curry( functionfilterReducer(predicateFn,combinationFn){
285
-
returnfunctionreducer(list,val){
286
-
if (predicateFn( val )) returncombinationFn( list, val );
287
-
return list;
288
-
};
289
-
} );
286
+
var curriedFilterReducer =curry(
287
+
functionfilterReducer(predicateFn,combinationFn){
288
+
returnfunctionreducer(list,val){
289
+
if (predicateFn( val )) returncombinationFn( list, val );
**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!
470
+
**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](ch4.md/#user-content-generalcompose), 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
471
469
472
#### List Combination: Pure vs. Impure
470
473
@@ -491,7 +494,7 @@ Thinking about `listCombination(..)` in isolation, there's no question it's impu
491
494
492
495
`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
496
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.
497
+
Back in [Chapter 5](ch5.md), 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
498
496
499
`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
500
@@ -548,12 +551,13 @@ var transduceMap = curry( function mapReducer(mapperFn,combinationFn){
548
551
};
549
552
} );
550
553
551
-
var transduceFilter =curry( functionfilterReducer(predicateFn,combinationFn){
552
-
returnfunctionreducer(list,v){
553
-
if (predicateFn( v )) returncombinationFn( list, v );
554
-
return list;
555
-
};
556
-
} );
554
+
var transduceFilter =curry(
555
+
functionfilterReducer(predicateFn,combinationFn){
556
+
returnfunctionreducer(list,v){
557
+
if (predicateFn( v )) returncombinationFn( list, v );
558
+
return list;
559
+
};
560
+
} );
557
561
```
558
562
559
563
Also recall that we use them like this:
@@ -597,7 +601,7 @@ Not bad, huh!? See the `listCombination(..)` and `strConcat(..)` functions used
597
601
598
602
### Transducers.js
599
603
600
-
Finally, let's illustrate our running example using the `transducers-js` library(https://github.com/cognitect-labs/transducers-js):
604
+
Finally, let's illustrate our running example using the [`transducers-js` library](https://github.com/cognitect-labs/transducers-js):
601
605
602
606
```js
603
607
var transformer =transducers.comp(
@@ -615,7 +619,7 @@ transducers.transduce( transformer, strConcat, "", words );
615
619
616
620
Looks almost identical to above.
617
621
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.
622
+
**Note:** The preceding snippet uses `transformers.comp(..)` because the library provides it, but in this case our [`compose(..)` from Chapter 4](ch4.md/#user-content-generalcompose) would produce the same outcome. In other words, composition itself isn't a transducing-sensitive operation.
619
623
620
624
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.
Copy file name to clipboardExpand all lines: apB.md
+9-9Lines changed: 9 additions & 9 deletions
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
# Functional-Light JavaScript
2
2
# Appendix B: The Humble Monad
3
3
4
-
Let me just start off this appendix by admitting: I did not know much about what a monad was before starting to write this appendix. And it took a lot of mistakes to get something sensible. If you don't believe me, go look at the commit history of this appendix in the Git repo for this book(https://github.com/getify/Functional-Light-JS)!
4
+
Let me just start off this appendix by admitting: I did not know much about what a monad was before starting to write this appendix. And it took a lot of mistakes to get something sensible. If you don't believe me, go look at the commit history of this appendix in the [Github repository for this book](https://github.com/getify/Functional-Light-JS)!
5
5
6
6
I am including the topic of monads in the book because it's part of the journey that every developer will encounter while learning FP, just as I have in this book writing.
7
7
@@ -29,7 +29,7 @@ I'm going to use the notion of data structures very loosely here, and assert tha
29
29
30
30
A monad is a data structure. It's a type. It's a set of behaviors that are specifically designed to make working with a value predictable.
31
31
32
-
Recall in Chapter 9 that we talked about functors: a value along with a map-like utility to perform an operation on all its constitute data members. A monad is a functor that includes some additional behavior.
32
+
Recall in [Chapter 9 that we talked about functors](ch9.md/#a-word-functors): a value along with a map-like utility to perform an operation on all its constitute data members. A monad is a functor that includes some additional behavior.
33
33
34
34
## Loose Interface
35
35
@@ -82,7 +82,7 @@ Don't worry if most of this doesn't make sense right now. We're not gonna obsess
82
82
83
83
All monad instances will have `map(..)`, `chain(..)` (also called `bind(..)` or `flatMap(..)`), and `ap(..)` methods. The purpose of these methods and their behavior is to provide a standardized way of multiple monad instances interacting with each other.
84
84
85
-
Let's look first at the monadic `map(..)` function. Like `map(..)` on an array (see Chapter 9) that calls a mapper function with its value(s) and produces a new array, a monad's `map(..)` calls a mapper function with the monad's value, and whatever is returned is wrapped in a new Just monad instance:
85
+
Let's look first at the monadic `map(..)` function. Like `map(..)` on an array (see [Chapter 9](ch9.md/#map)) that calls a mapper function with its value(s) and produces a new array, a monad's `map(..)` calls a mapper function with the monad's value, and whatever is returned is wrapped in a new Just monad instance:
86
86
87
87
```js
88
88
varA=Just( 10 );
@@ -103,7 +103,7 @@ typeof eleven; // "number"
103
103
104
104
`eleven` is the actual primitive number `11`, not a monad holding that value.
105
105
106
-
To connect this `chain(..)` method conceptually to stuff we've already learned, we'll point out that many monad implementations name this method `flatMap(..)`. Now, recall from Chapter 9 what `flatMap(..)` does (as compared to `map(..)`) with an array:
106
+
To connect this `chain(..)` method conceptually to stuff we've already learned, we'll point out that many monad implementations name this method `flatMap(..)`. Now, recall from [Chapter 9 what `flatMap(..)`](ch9.md/#user-content-flatmap) does (as compared to `map(..)`) with an array:
107
107
108
108
```js
109
109
var x = [3];
@@ -116,7 +116,7 @@ See the difference? The mapper function `v => [v,v+1]` results in a `[3,4]` arra
116
116
117
117
That's the same kind of thing going on with a monad's `chain(..)` (aka `flatMap(..)`). Instead of getting a monad holding the value as `map(..)` does, `chain(..)` additionally flattens the monad into the underlying value. Actually, instead of creating that intermediate monad only to immediately flatten it, `chain(..)` is generally implemented more performantly to just take a shortcut and not create the monad in the first place. Either way, the end result is the same.
118
118
119
-
One way to illustrate `chain(..)` in this manner is in combination with the `identity(..)` utility (see Chapter 3), to effectively extract a value from a monad:
119
+
One way to illustrate `chain(..)` in this manner is in combination with the `identity(..)` utility (see [Chapter 3](ch3.md/#one-on-one)), to effectively extract a value from a monad:
120
120
121
121
```js
122
122
varidentity=v=> v;
@@ -150,7 +150,7 @@ Now, how could we make a new monad where the values `10` and `3` had been added
150
150
151
151
To use `ap(..)`, we said we first need to construct a monad that holds a function. Specifically, we need one that holds a function that itself holds (remembers via closure) the value in `A`. Let that sink in for a moment.
152
152
153
-
To make a monad from `A` that holds a value-containing function, we'll call `A.map(..)`, giving it a curried function that "remembers" that extracted value (see Chapter 3) as its first argument. We'll call this new function-containing monad `C`:
153
+
To make a monad from `A` that holds a value-containing function, we'll call `A.map(..)`, giving it a curried function that "remembers" that extracted value (see [Chapter 3](ch3.md/#one-at-a-time)) as its first argument. We'll call this new function-containing monad `C`:
154
154
155
155
```js
156
156
functionsum(x,y) { return x + y; }
@@ -187,7 +187,7 @@ var D = B.map( A.chain( curry( sum ) ) );
187
187
D.inspect(); // Just(13);
188
188
```
189
189
190
-
And that of course is just a composition (see Chapter 4):
190
+
And that of course is just a composition (see [Chapter 4](ch4.md)):
191
191
192
192
```js
193
193
varD=compose( B.map, A.chain, curry )( sum );
@@ -249,7 +249,7 @@ Maybe.of( someObj )
249
249
.map( console.log );
250
250
```
251
251
252
-
In other words, if at any point in the chain we get a `null`/`undefined` value, the Maybe magically switches into no-op mode -- it's now a `Nothing()` monad instance! -- and stops doing anything for the rest of the chain. That makes the nestedproperty access safe against throwing JS exceptions if some property is missing/empty. That's cool, and a nice helpful abstraction for sure!
252
+
In other words, if at any point in the chain we get a `null`/`undefined` value, the Maybe magically switches into no-op mode -- it's now a `Nothing()` monad instance! -- and stops doing anything for the rest of the chain. That makes the nested-property access safe against throwing JS exceptions if some property is missing/empty. That's cool, and a nice helpful abstraction for sure!
253
253
254
254
But... that approach to Maybe is not a pure monad.
255
255
@@ -259,7 +259,7 @@ The earlier implementation of the Maybe monad I provided differs from other Mayb
259
259
260
260
So wait. If we don't get the automatic short-circuting, why is Maybe useful at all?!? That seems like its whole point.
261
261
262
-
Never fear! We can simply provide the empty-check externally, and the rest of the short-circuting behavior of the Maybe monad will work just fine. Here's how you could do the `someObj.something.else.entirely` nested-property access from before, but more "correctly":
262
+
Never fear! We can simply provide the empty-check externally, and the rest of the short-circuting behavior of the Maybe monad will work just fine. Here's how you could do the nested-property access (`someObj.something.else.entirely`) from before, but more "correctly":
0 commit comments