Skip to content

Commit 3a7ae2a

Browse files
committed
2 parents 054bfd8 + 56d882d commit 3a7ae2a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1258
-648
lines changed

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
1-
# functional-examples
2-
Examples with Functional JavaScript
1+
### functional-examples
2+
3+
4+
5+
6+
7+
## [New functional library `cpsfy`](https://github.com/dmitriz/cpsfy)
8+
9+
UPDATE. Check out my new advanced functional library:
10+
https://github.com/dmitriz/cpsfy
11+
12+
# Examples with Functional JavaScript
313

414
Heavily annotated examples from the awesome videos
515
<a href="https://egghead.io/lessons/javascript-refactoring-imperative-code-to-a-single-composed-expression-using-box">Professor Frisby Introduces Composable Functional JavaScript</a>,
616
see also <a href="https://www.reddit.com/r/javascript/comments/5hfq6n/100_minutes_of_free_functional_programming/">the discussion on Reddit</a> and <a href="https://news.ycombinator.com/item?id=13167149">on Hacker News</a>.
717

818
See also the wonderful e-book <a href="https://drboolean.gitbooks.io/mostly-adequate-guide/content/">
919
Professor Frisby's Mostly Adequate Guide to Functional Programming</a>
10-
by the same author.
20+
by the same author (See also https://github.com/MostlyAdequate/mostly-adequate-guide for the updates).
1121

1222

1323
## What makes Prof. Frisby's course and book awesome?
14-
1524
Both course and book amazingly manage to avoid suffering from the two widespread diseases when explaining abstract functional concepts:
1625

1726
- The first disease is to stay with abstract artifically simplified examples that can be easily manipulated
@@ -29,7 +38,6 @@ demonstrating real benefits of the functional abstractions with sharp focus and
2938

3039

3140
## Running Examples
32-
3341
Install the packages with either of
3442
```js
3543
yarn install
@@ -42,5 +50,4 @@ node <file.js>
4250

4351

4452
## Related Projects
45-
4653
- [Monadic Libraries Examples with Tests](https://github.com/dmitriz/monadic-libraries-examples)

examples/01-box.js renamed to examples/01-identity-functor-box.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ const Box = x => (
1111
// so we can keep chaining
1212
map: f => Box(f(x)),
1313

14-
// fold functor map, applies f and returns the raw unwrapped value,
14+
// not part of the functor spec
15+
// applies f and returns the raw unwrapped value,
1516
// sends f:a->b into fold(f):Box(a)->b,
1617
// does not return any Box container, so can't be chained with map
1718
fold: f => f(x),
@@ -35,13 +36,13 @@ const nextCharForNumberString = str =>
3536

3637
const result = nextCharForNumberString(' 64 ')
3738

38-
console.log('nextCharForNumberString for 64 is: ', result) //=> a
39+
console.log(`console.log displays nextCharForNumberString('64') as: `, result)
3940

4041

4142

4243
// ==== Experiments
4344

44-
console.log(Box(22))
45+
console.log(`Box(22) is displayed as: `, Box(22))
4546

4647
const inspectBox = x => (
4748
{
@@ -53,4 +54,4 @@ const inspectBox = x => (
5354
}
5455
)
5556

56-
console.log(inspectBox(4)) //=> I am wrapping '4'
57+
console.log(`Custom inspect shows 'inspectBox(4)' as: `, inspectBox(4)) //=> I am wrapping '4'

examples/02-refactor.js renamed to examples/02-identity-double-wrap.js

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,25 @@ const Box = x => (
1010
// so we can keep chaining
1111
map: f => Box(f(x)),
1212

13-
// fold functor map, applies f and returns the raw unwrapped value,
13+
// not part of functor spec
14+
// applies f and returns the raw unwrapped value,
1415
// sends f:a->b into fold(f):Box(a)->b,
1516
// does not return any Box container, so can't be chained with map
1617
fold: f => f(x),
1718

1819
// custom getter function -- called by console.log
19-
inspect: () => `Box(${x})`
20+
inspect: () => x.inspect ? `Box(${x.inspect()})` : `Box(${x})`
2021
}
2122
)
2223

23-
24+
// String => Box(String)
2425
const moneyToFloat = str =>
2526
Box(str)
2627
.map(s => s.replace(/\$/g, ''))
2728
.map(r => parseFloat(r))
2829
// .fold(r => parseFloat(r))
2930

30-
31+
// String => Box(String)
3132
const percentToFloat = str =>
3233
Box(str.replace(/\%/g, ''))
3334
.map(replaced => parseFloat(replaced))
@@ -59,10 +60,26 @@ const applyDiscount = (price, discount) =>
5960
)
6061

6162

62-
console.log(moneyToFloat(' $33 ')) //=> Box(33)
63-
console.log(percentToFloat(' 1.23% ')) //=> Box(0.0123)
63+
// without fold, result is wrapped into Box twice
64+
const applyDiscountInBox = (price, discount) =>
65+
moneyToFloat(price).map(cost =>
66+
67+
// nesting so cost remains in scope
68+
percentToFloat(discount).map(savings =>
69+
cost - cost * savings
70+
)
71+
)
72+
73+
74+
console.log(`moneyToFloat(' $33 ') : `, moneyToFloat(' $33 ')) //=> Box(33)
75+
console.log(`percentToFloat(' 1.23% ') : `, percentToFloat(' 1.23% ')) //=> Box(0.0123)
6476

6577
const result = applyDiscount('$55', '20%')
6678

67-
console.log(result) //=> 44
79+
console.log(`applyDiscount('$55', '20%') : `, result) //=> 44
80+
81+
console.log(
82+
`applyDiscountInBox('$55', '20%') : `,
83+
applyDiscountInBox('$55', '20%')
84+
) //=> Box(Box(44))
6885

examples/03-right-left.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const Right = x => (
1515
const Left = x => (
1616
{
1717

18-
// Left ignores f, simply passes x itself
18+
// Left ignores f, simply passes ahead raw value x
1919
map: f => Left(x),
2020

2121
// applies the function on the left and returns raw value
@@ -37,8 +37,8 @@ const resultLeft = Left(3)
3737
.map(x => x + 1)
3838
.map(x => x / 2)
3939

40-
console.log(resultRight) //=> Right(2)
41-
console.log(resultLeft) //=> Left(3)
40+
console.log(`Right(3) transformed: `, resultRight) //=> Right(2)
41+
console.log(`Left(3) transformed: `,resultLeft) //=> Left(3)
4242

4343

4444
// passing Right value,
@@ -51,6 +51,8 @@ const result = Right(2)
5151
// left function handles error, right function returns the value
5252
.fold(x => 'error', x => x)
5353

54+
console.log(`Right(2) transformed and unwrapped with (x => 'error', x => x): `, result) //=> 1.5
55+
5456

5557
// passing Left value,
5658
// so all functions are ignored
@@ -62,9 +64,7 @@ const resultError = Left(2)
6264
// left function handles error, right function returns the value
6365
.fold(x => 'error', x => x)
6466

65-
console.log(result) //=> 1.5
66-
console.log(resultError) //=> error
67-
67+
console.log(`Left(2) transformed and unwrapped with (x => 'error', x => x): `, resultError) //=> error
6868

6969

7070
const findColor = name =>
@@ -78,15 +78,18 @@ const resultColor = findColor('red')
7878
.slice(1)
7979
.toUpperCase()
8080

81-
console.log(resultColor)
81+
console.log(`findColor('red') transformed is: `, resultColor)
8282

8383

84-
const findColorFixed = name => {
84+
const findColorCases = name => {
8585
const found = {red: '#ff4444', blue: '#00ff00'}[name]
8686
return found ? Right(found) : Left(null)
8787
}
8888

89-
const badColor = findColorFixed('gray')
89+
console.log(`findColorCases('gray') is: `, findColorCases('gray')) //=> no color
90+
91+
92+
const badColor = findColorCases('gray')
9093

9194
// map only applies when passed Right,
9295
// ignored when passed Left
@@ -96,21 +99,21 @@ const badColor = findColorFixed('gray')
9699
c => c.toUpperCase()
97100
)
98101

99-
console.log(badColor) //=> no color
102+
console.log(`findColorCases('gray') transformed and fold with (e => 'no color', c => c.toUpperCase()) is: `, badColor) //=> no color
100103

101104

102105

103-
// ensure null will always go Left
106+
// ensure null (of any falsey value) will always go to Left
104107
const fromNullable = x =>
105108
x != null ? Right(x) : Left(null)
106109

107110
// no need anymore to worry about null in our logic
108111
// much simpler function, no more cases
109-
const findColorNew = name =>
112+
const findColorFromNullable = name =>
110113
fromNullable({red: '#ff4444', blue: '#00ff00'}[name])
111114

112115

113-
const badColorNew = findColorNew('gray')
116+
const badColorNew = findColorFromNullable('gray')
114117

115118
// map only applies when passed Right,
116119
// ignored when passed Left
@@ -120,5 +123,8 @@ const badColorNew = findColorNew('gray')
120123
c => c.toUpperCase()
121124
)
122125

123-
console.log(badColorNew) //=> no color
126+
console.log(
127+
`findColorFromNullable('gray') transformed and fold with (e => 'no color', c => c.toUpperCase()) is: `,
128+
badColorNew
129+
) //=> no color
124130

examples/04-fs-error-handling.js

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const getPort = fileName =>
5151

5252
// this will not "explode" if fileName is not found!
5353
tryCatch( () => fs.readFileSync(fileName) )
54-
.map(c => JSON.parse(c))
54+
.map( c => JSON.parse(c) )
5555

5656
// .map(c => tryCatch(() => JSON.parse(c)))
5757
.fold(
@@ -61,25 +61,30 @@ const getPort = fileName =>
6161
c => c.port
6262
)
6363

64-
console.log(getPort()) //=> 3000
65-
console.log(getPort('con.json')) //=> 3000
66-
console.log(getPort('config.json')) //=> 8080
64+
// no file passed
65+
console.log(`getPort() : `, getPort()) //=> 3000
6766

67+
// file not present
68+
console.log(`getPort('con.json') : `, getPort('con.json')) //=> 3000
6869

70+
// file present
71+
console.log(`getPort('config.json') : `, getPort('config.json')) //=> 8080
6972

70-
const getPortNew = fileName =>
71-
tryCatch(() => fs.readFileSync(fileName))
72-
.chain(c => tryCatch(() => JSON.parse(c)))
73+
74+
75+
// protecting against further errors by wrapping into tryCatch
76+
const getPortSafe = fileName =>
77+
tryCatch( () => fs.readFileSync(fileName) )
78+
.chain( c => tryCatch(() => JSON.parse(c)) )
7379
.fold(
7480
e => 3000,
7581
c => c.port
7682
)
7783

7884

79-
console.log(getPortNew()) //=> 3000
80-
console.log(getPortNew('con.json')) //=> 3000
85+
console.log(`getPortSafe() : `, getPortSafe()) //=> 3000
86+
console.log(`getPortSafe('con.json') : `, getPortSafe('con.json')) //=> 3000
8187

8288
// importing badly formatted file
83-
console.log(getPortNew('configBad.json')) //=> 3000
84-
console.log(getPort('config.json')) //=> 8080
85-
89+
console.log(`getPortSafe('configBad.json') : `, getPortSafe('configBad.json')) //=> 3000
90+
console.log(`getPortSafe('config.json') : `, getPortSafe('config.json')) //=> 8080

0 commit comments

Comments
 (0)