A Path to Point-Free JS
web :: rwp.im
github :: rpearce
email :: me@robertwpearce.com
tweeter :: @RobertWPearce
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 1
about me
Hi, I'm Robert
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
about me
Hi, I'm Robert
• My kiwi wife & I have recently moved back from New Zealand
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
about me
Hi, I'm Robert
• My kiwi wife & I have recently moved back from New Zealand
• Started out at Jack Russell Software w/ Tom Wilson in 2011
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
about me
Hi, I'm Robert
• My kiwi wife & I have recently moved back from New Zealand
• Started out at Jack Russell Software w/ Tom Wilson in 2011
• Currently with Articulate, the e-learning software company
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
about me
Hi, I'm Robert
• My kiwi wife & I have recently moved back from New Zealand
• Started out at Jack Russell Software w/ Tom Wilson in 2011
• Currently with Articulate, the e-learning software company
• Work with FP in JS all day
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
about me
Hi, I'm Robert
• My kiwi wife & I have recently moved back from New Zealand
• Started out at Jack Russell Software w/ Tom Wilson in 2011
• Currently with Articulate, the e-learning software company
• Work with FP in JS all day
• Learn Haskell (λ) and other FP in what little free time I have
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
about me
Hi, I'm Robert
• My kiwi wife & I have recently moved back from New Zealand
• Started out at Jack Russell Software w/ Tom Wilson in 2011
• Currently with Articulate, the e-learning software company
• Work with FP in JS all day
• Learn Haskell (λ) and other FP in what little free time I have
• Pretend to know what I'm doing
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
Question:
what are we going to do?
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 3
Answer:
ultimately attempt to simplify this code
// bestFilmsHtml :: [Film] -> [Html]
const bestFilmsHtml = films => {
const bestFilms = films.filter(film => film.rating >= 8.8)
const filmsHtml = bestFilms.map(film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
)
return filmsHtml
}
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 4
Answer:
which takes this data...
const films = [
{ title: 'The Empire Strikes Back', rating: 8.8 },
{ title: 'Pulp Fiction', rating: 8.9 },
{ title: 'The Deer Hunter', rating: 8.2 },
{ title: 'The Lion King', rating: 8.5 }
]
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 5
Answer:
...and returns this
[
"<div>The Empire Strikes Back, <strong>8.8</strong></div>",
"<div>Pulp Fiction, <strong>8.9</strong></div>"
]
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 6
Question:
how are we going to do that?
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 7
Answer:
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 8
Answer:
•
!
currying!
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 8
Answer:
•
!
currying!
•
"
composition!
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 8
Answer:
•
!
currying!
•
"
composition!
•
# $
eta-conversion!
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 8
Answer:
•
!
currying!
•
"
composition!
•
# $
eta-conversion!
•
%
ramda.js!
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 8
Answer:
•
!
currying!
•
"
composition!
•
# $
eta-conversion!
•
%
ramda.js!
• ...this is our Friday night
✨
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 8
Question:
what does point-free mean?
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 9
Tacit programming, also called point-free
style, is a programming paradigm in which
function definitions do not identify the
arguments (or "points") on which they
operate. Instead the definitions merely
compose other functions, among which are
combinators that manipulate the arguments.
— https://en.wikipedia.org/wiki/Tacit_programming
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 10
Combinatory logic is a notation to eliminate the
need for quantified variables in mathematical logic
[...] based on combinators which were introduced by
Schönfinkel in 1920 with the idea of providing an
analogous way to build up functions - and to remove
any mention of variables - particularly in predicate
logic. A combinator is a higher-order function that
uses only function application and earlier defined
combinators to define a result from its arguments.
— https://en.wikipedia.org/wiki/Combinatory_logic
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 11
...there are other opinions
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 12
The lack of argument naming gives
point-free style a reputation of being
unnecessarily obscure, hence the
epithet "pointless style".
— https://en.wikipedia.org/wiki/Tacit_programming
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 13
some may disagree
"Point-Free or Die: Tacit Programming in Haskell and Beyond"
https://www.youtube.com/watch?v=seVSlKazsNk
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 14
Question:
what does it look like?
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 15
(do we care about the implementation?)
(say yes)
const films = [
{ title: 'The Empire Strikes Back', rating: 8.8 },
{ title: 'Pulp Fiction', rating: 8.9 },
{ title: 'The Deer Hunter', rating: 8.2 },
{ title: 'The Lion King', rating: 8.5 }
]
bestFilmsHtml(films)
// => [
// "<div>The Empire Strikes Back, <strong>8.8</strong></div>",
// "<div>Pulp Fiction, <strong>8.9</strong></div>"
// ]
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 16
(do we care about the implementation?)
(say yes)
const films = [
{ title: 'The Empire Strikes Back', rating: 8.8 },
{ title: 'Pulp Fiction', rating: 8.9 },
{ title: 'The Deer Hunter', rating: 8.2 },
{ title: 'The Lion King', rating: 8.5 }
]
bestFilmsHtml(films)
// => [
// "<div>The Empire Strikes Back, <strong>8.8</strong></div>",
// "<div>Pulp Fiction, <strong>8.9</strong></div>"
// ]
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 16
bestFilmsHtml
// bestFilmsHtml :: [Film] -> [Html]
const bestFilmsHtml =
compose(filmsHtml, onlyHighRatings)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 17
onlyHighRatings
// onlyHighRatings :: [Film] -> [Film]
const onlyHighRatings =
filter(hasHighRating)
// bestFilmsHtml :: [Film] -> [Html]
const bestFilmsHtml =
compose(filmsHtml, onlyHighRatings)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 18
hasHighRating
// hasHighRating :: Film -> Bool
const hasHighRating =
propSatisfies(gte(__, 8.8), 'rating')
// onlyHighRatings :: [Film] -> [Film]
const onlyHighRatings =
filter(hasHighRating)
// bestFilmsHtml :: [Film] -> [Html]
const bestFilmsHtml =
compose(filmsHtml, onlyHighRatings)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 19
filmsHtml
// filmsHtml :: [Film] -> [Html]
const filmsHtml =
map(filmHtml)
// hasHighRating :: Film -> Bool
const hasHighRating =
propSatisfies(gte(__, 8.8), 'rating')
// onlyHighRatings :: [Film] -> [Film]
const onlyHighRatings =
filter(hasHighRating)
// bestFilmsHtml :: [Film] -> [Html]
const bestFilmsHtml =
compose(filmsHtml, onlyHighRatings)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 20
filmHtml (notice any difference?)
// filmHtml :: Film -> Html
const filmHtml = film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
// filmsHtml :: [Film] -> [Html]
const filmsHtml =
map(filmHtml)
// hasHighRating :: Film -> Bool
const hasHighRating =
propSatisfies(gte(__, 8.8), 'rating')
// onlyHighRatings :: [Film] -> [Film]
const onlyHighRatings =
filter(hasHighRating)
// bestFilmsHtml :: [Film] -> [Html]
const bestFilmsHtml =
compose(filmsHtml, onlyHighRatings)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 21
filmHtml (notice any difference?)
// filmHtml :: Film -> Html
const filmHtml = film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
// filmsHtml :: [Film] -> [Html]
const filmsHtml =
map(filmHtml)
// hasHighRating :: Film -> Bool
const hasHighRating =
propSatisfies(gte(__, 8.8), 'rating')
// onlyHighRatings :: [Film] -> [Film]
const onlyHighRatings =
filter(hasHighRating)
// bestFilmsHtml :: [Film] -> [Html]
const bestFilmsHtml =
compose(filmsHtml, onlyHighRatings)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 21
!
buckle up
it's time for prerequisites
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 22
currying
!
and partial application
const add = (a, b) => b + a
const add2 = add.bind(null, 2)
add2(3) // 5
add2(5) // 7
const add = a => b => b + a
const add3 = add(3)
add3(2) // 5
add3(4) // 7
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
currying
!
and partial application
const add = (a, b) => b + a
const add2 = add.bind(null, 2)
add2(3) // 5
add2(5) // 7
const add = a => b => b + a
const add3 = add(3)
add3(2) // 5
add3(4) // 7
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
currying
!
and partial application
const add = (a, b) => b + a
const add2 = add.bind(null, 2)
add2(3) // 5
add2(5) // 7
const add = a => b => b + a
const add3 = add(3)
add3(2) // 5
add3(4) // 7
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
currying
!
and partial application
const add = (a, b) => b + a
const add2 = add.bind(null, 2)
add2(3) // 5
add2(5) // 7
const add = a => b => b + a
const add3 = add(3)
add3(2) // 5
add3(4) // 7
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
currying
!
and partial application
const add = (a, b) => b + a
const add2 = add.bind(null, 2)
add2(3) // 5
add2(5) // 7
const add = a => b => b + a
const add3 = add(3)
add3(2) // 5
add3(4) // 7
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
currying
!
and partial application
const add = (a, b) => b + a
const add2 = add.bind(null, 2)
add2(3) // 5
add2(5) // 7
const add = a => b => b + a
const add3 = add(3)
add3(2) // 5
add3(4) // 7
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
currying
!
and partial application
const add = (a, b) => b + a
const add2 = add.bind(null, 2)
add2(3) // 5
add2(5) // 7
const add = a => b => b + a
const add3 = add(3)
add3(2) // 5
add3(4) // 7
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
currying
!
and partial application
const add = (a, b) => b + a
const add2 = add.bind(null, 2)
add2(3) // 5
add2(5) // 7
const add = a => b => b + a
const add3 = add(3)
add3(2) // 5
add3(4) // 7
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
In mathematics and computer
science, currying is the technique of
translating the evaluation of a function
that takes multiple arguments into
evaluating a sequence of functions,
each with a single argument.
— https://en.wikipedia.org/wiki/Currying
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 24
currying
!
and partial application
const add = a => b => b + a
// is equivalent to
function add(a) {
return function(b) {
return b + a
}
}
// but...
add(2)(3) // 5
add(2, 3) // function add() ... ( °□°
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 25
currying
!
and partial application
const add = a => b => b + a
// is equivalent to
function add(a) {
return function(b) {
return b + a
}
}
// but...
add(2)(3) // 5
add(2, 3) // function add() ... ( °□°
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 25
currying
!
and partial application
const add = a => b => b + a
// is equivalent to
function add(a) {
return function(b) {
return b + a
}
}
// but...
add(2)(3) // 5
add(2, 3) // function add() ... ( °□°
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 25
currying
!
and partial application
const add = a => b => b + a
// is equivalent to
function add(a) {
return function(b) {
return b + a
}
}
// but...
add(2)(3) // 5
add(2, 3) // function add() ... ( °□°
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 25
currying
!
and partial application
const add = a => b => b + a
// is equivalent to
function add(a) {
return function(b) {
return b + a
}
}
// but...
add(2)(3) // 5
add(2, 3) // function add() ... ( °□°
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 25
currying
!
and partial application
const add = a => b => b + a
// is equivalent to
function add(a) {
return function(b) {
return b + a
}
}
// but...
add(2)(3) // 5
add(2, 3) // function add() ... ( °□°
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 25
autocurrying
// add.js
import { curry } from 'ramda'
const add = (a, b) => b + a
export default curry(add)
// some other file
import add from './add'
add(2, 3) // 5
add(2)(3) // 5
add()(2)(3) // 5
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
autocurrying
// add.js
import { curry } from 'ramda'
const add = (a, b) => b + a
export default curry(add)
// some other file
import add from './add'
add(2, 3) // 5
add(2)(3) // 5
add()(2)(3) // 5
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
autocurrying
// add.js
import { curry } from 'ramda'
const add = (a, b) => b + a
export default curry(add)
// some other file
import add from './add'
add(2, 3) // 5
add(2)(3) // 5
add()(2)(3) // 5
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
autocurrying
// add.js
import { curry } from 'ramda'
const add = (a, b) => b + a
export default curry(add)
// some other file
import add from './add'
add(2, 3) // 5
add(2)(3) // 5
add()(2)(3) // 5
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
autocurrying
// add.js
import { curry } from 'ramda'
const add = (a, b) => b + a
export default curry(add)
// some other file
import add from './add'
add(2, 3) // 5
add(2)(3) // 5
add()(2)(3) // 5
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
autocurrying
// add.js
import { curry } from 'ramda'
const add = (a, b) => b + a
export default curry(add)
// some other file
import add from './add'
add(2, 3) // 5
add(2)(3) // 5
add()(2)(3) // 5
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
autocurrying
// add.js
import { curry } from 'ramda'
const add = (a, b) => b + a
export default curry(add)
// some other file
import add from './add'
add(2, 3) // 5
add(2)(3) // 5
add()(2)(3) // 5
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
autocurrying
// add.js
import { curry } from 'ramda'
const add = (a, b) => b + a
export default curry(add)
// some other file
import add from './add'
add(2, 3) // 5
add(2)(3) // 5
add()(2)(3) // 5
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
autocurrying
// add.js
import { curry } from 'ramda'
const add = (a, b) => b + a
export default curry(add)
// some other file
import add from './add'
add(2, 3) // 5
add(2)(3) // 5
add()(2)(3) // 5
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
autocurrying
// add.js
import { curry } from 'ramda'
const add = (a, b) => b + a
export default curry(add)
// some other file
import add from './add'
add(2, 3) // 5
add(2)(3) // 5
add()(2)(3) // 5
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
https://rwp.im/ramda-chops-function-currying.html
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 27
sweet! who cares about currying?
currying helps with composition!
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 28
composition
import add from './add'
import mult from './mult'
const add2 = add(2) // partial application
const mult3 = mult(3) // partial application
mult3(add2(10)) // composition & full application
// returns 36
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 29
composition
import add from './add'
import mult from './mult'
const add2 = add(2) // partial application
const mult3 = mult(3) // partial application
mult3(add2(10)) // composition & full application
// returns 36
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 29
composition
import add from './add'
import mult from './mult'
const add2 = add(2) // partial application
const mult3 = mult(3) // partial application
mult3(add2(10)) // composition & full application
// returns 36
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 29
how could we reuse this
composition?
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 30
mult3(add2(10))
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 31
// mult3(add2(10))
const compose = (f, g) => x =>
f(g(x))
const add2mult3 =
compose(mult3, add2)
add2mult3(10) // 36
add2mult3(42) // 132
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 32
// mult3(add2(10))
const compose = (f, g) => x =>
f(g(x))
const add2mult3 =
compose(mult3, add2)
add2mult3(10) // 36
add2mult3(42) // 132
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 32
// mult3(add2(10))
const compose = (f, g) => x =>
f(g(x))
const add2mult3 =
compose(mult3, add2)
add2mult3(10) // 36
add2mult3(42) // 132
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 32
// mult3(add2(10))
const compose = (f, g) => x =>
f(g(x))
const add2mult3 =
compose(mult3, add2)
add2mult3(10) // 36
add2mult3(42) // 132
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 32
// mult3(add2(10))
const compose = (f, g) => x =>
f(g(x))
const add2mult3 =
compose(mult3, add2)
add2mult3(10) // 36
add2mult3(42) // 132
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 32
// mult3(add2(10))
const compose = (f, g) => x =>
f(g(x))
const add2mult3 =
compose(mult3, add2)
add2mult3(10) // 36
add2mult3(42) // 132
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 32
our compose isn't variadic, though
let's reach for a tool
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 33
composition
import { compose } from 'ramda'
import add from './add'
import mult from './mult'
const add2 = add(2)
const mult3 = mult(3)
const add2mult3Repeated =
compose(mult3, add2, mult3, add2)
add2mult3Repeated(10) // 114
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 34
composition
import { compose } from 'ramda'
import add from './add'
import mult from './mult'
const add2 = add(2)
const mult3 = mult(3)
const add2mult3Repeated =
compose(mult3, add2, mult3, add2)
add2mult3Repeated(10) // 114
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 34
composition
import { compose } from 'ramda'
import add from './add'
import mult from './mult'
const add2 = add(2)
const mult3 = mult(3)
const add2mult3Repeated =
compose(mult3, add2, mult3, add2)
add2mult3Repeated(10) // 114
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 34
reading composition
compose(f, g)(x) ≡ f(g(x))
Say it with me, "f after g"
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 35
https://rwp.im/ramda-chops-function-composition.html
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 36
eta-conversion
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 37
eta-conversion
• "An eta conversion (also written η-conversion) is adding or
dropping of abstraction over a function."
https://wiki.haskell.org/Eta_conversion
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 37
eta-conversion
• "An eta conversion (also written η-conversion) is adding or
dropping of abstraction over a function."
https://wiki.haskell.org/Eta_conversion
• eta-abstraction
!
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 37
eta-conversion
• "An eta conversion (also written η-conversion) is adding or
dropping of abstraction over a function."
https://wiki.haskell.org/Eta_conversion
• eta-abstraction
!
• eta-reduction
"
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 37
eta-abstraction
Remember this?
const add2mult3 =
compose(mult3, add2)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 38
eta-abstraction
Remember this?
const add2mult3 =
compose(mult3, add2)
Here's how we would perform an eta-abstraction:
const add2mult3 = n =>
compose(mult3, add2)(n)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 39
eta-reduction
Let's take our recent eta-abstraction,
const add2mult3 = n =>
compose(mult3, add2)(n)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 40
eta-reduction
Let's take our recent eta-abstraction,
const add2mult3 = n =>
compose(mult3, add2)(n)
and perform an eta-reduction:
const add2mult3 =
compose(mult3, add2)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 41
!
we covered the prerequisites!
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 42
let's talk about ramda.js
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 43
let's talk about ramda.js
• functional library
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 43
let's talk about ramda.js
• functional library
• easy to make functional pipelines
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 43
let's talk about ramda.js
• functional library
• easy to make functional pipelines
• never mutates data
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 43
let's talk about ramda.js
• functional library
• easy to make functional pipelines
• never mutates data
• all functions are automatically curried
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 43
let's talk about ramda.js
• functional library
• easy to make functional pipelines
• never mutates data
• all functions are automatically curried
• parameters to functions make it convenient for currying; data to
be operated on generally comes last, for example
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 43
useful ramda functions for us right now
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
useful ramda functions for us right now
• curry :: (* → a) → (* → a)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
useful ramda functions for us right now
• curry :: (* → a) → (* → a)
• compose :: ((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b,
…, n) → z)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
useful ramda functions for us right now
• curry :: (* → a) → (* → a)
• compose :: ((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b,
…, n) → z)
• filter :: Filterable f => (a → Boolean) → f a → f a
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
useful ramda functions for us right now
• curry :: (* → a) → (* → a)
• compose :: ((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b,
…, n) → z)
• filter :: Filterable f => (a → Boolean) → f a → f a
• map :: Functor f => (a → b) → f a → f b
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
useful ramda functions for us right now
• curry :: (* → a) → (* → a)
• compose :: ((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b,
…, n) → z)
• filter :: Filterable f => (a → Boolean) → f a → f a
• map :: Functor f => (a → b) → f a → f b
• propSatisfies :: (a → Boolean) → String → {String: a} → Boolean
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
useful ramda functions for us right now
• curry :: (* → a) → (* → a)
• compose :: ((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b,
…, n) → z)
• filter :: Filterable f => (a → Boolean) → f a → f a
• map :: Functor f => (a → b) → f a → f b
• propSatisfies :: (a → Boolean) → String → {String: a} → Boolean
• gte :: Ord a => a → a → Boolean
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
useful ramda functions for us right now
• curry :: (* → a) → (* → a)
• compose :: ((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b,
…, n) → z)
• filter :: Filterable f => (a → Boolean) → f a → f a
• map :: Functor f => (a → b) → f a → f b
• propSatisfies :: (a → Boolean) → String → {String: a} → Boolean
• gte :: Ord a => a → a → Boolean
• __ (used to specify gaps within curried functions so that we can do partial
application regardless of argument order)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
our original code to refactor
// bestFilmsHtml :: [Film] -> [Html]
const bestFilmsHtml = films => {
const bestFilms = films.filter(film => film.rating >= 8.8)
const filmsHtml = bestFilms.map(film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
)
return filmsHtml
}
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 45
we got this
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 46
implicit return b/c variables weren't needed
const bestFilmsHtml = films =>
films
.filter(film => film.rating >= 8.8)
.map(film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 47
extract hasHighRating
const hasHighRating = film =>
film.rating >= 8.8
const bestFilmsHtml = films =>
films
.filter(hasHighRating)
.map(film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 48
refactor hasHighRating
import { __, gte, propSatisfies } from 'ramda'
const hasHighRating = film =>
propSatisfies(gte(__, 8.8), 'rating')(film)
const bestFilmsHtml = films =>
films
.filter(hasHighRating)
.map(film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 49
eta-reduce hasHighRating
import { __, gte, propSatisfies } from 'ramda'
const hasHighRating =
propSatisfies(gte(__, 8.8), 'rating')
const bestFilmsHtml = films =>
films
.filter(hasHighRating)
.map(film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 50
extract filmHtml
import { __, gte, propSatisfies } from 'ramda'
const filmHtml = film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
const hasHighRating =
propSatisfies(gte(__, 8.8), 'rating')
const bestFilmsHtml = films =>
films
.filter(hasHighRating)
.map(filmHtml)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 51
convert bestFilmsHtml to use compose
import { __, compose, filter, gte, map, propSatisfies } from 'ramda'
const filmHtml = film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
const hasHighRating =
propSatisfies(gte(__, 8.8), 'rating')
const bestFilmsHtml = films =>
compose(map(filmHtml), filter(hasHighRating))(films)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 52
eta-reduce bestFilmsHtml
import { __, compose, filter, gte, map, propSatisfies } from 'ramda'
const filmHtml = film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
const hasHighRating =
propSatisfies(gte(__, 8.8), 'rating')
const bestFilmsHtml =
compose(map(filmHtml), filter(hasHighRating))
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 53
we could stop here...
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 54
import { __, compose, filter, gte, map, propSatisfies } from 'ramda'
const filmHtml = film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
const hasHighRating =
propSatisfies(gte(__, 8.8), 'rating')
const bestFilmsHtml =
compose(map(filmHtml), filter(hasHighRating))
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 55
...but let's go for clarity & reuse
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 56
extract onlyHighRatings
import { __, compose, filter, gte, map, propSatisfies } from 'ramda'
const filmHtml = film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
const hasHighRating =
propSatisfies(gte(__, 8.8), 'rating')
const onlyHighRatings =
filter(hasHighRating)
const bestFilmsHtml =
compose(map(filmHtml), onlyHighRatings)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 57
extract filmsHtml
import { __, compose, filter, gte, map, propSatisfies } from 'ramda'
const filmHtml = film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
const filmsHtml =
map(filmHtml)
const hasHighRating =
propSatisfies(gte(__, 8.8), 'rating')
const onlyHighRatings =
filter(hasHighRating)
const bestFilmsHtml =
compose(filmsHtml, onlyHighRatings)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 58
did we miss one? does it matter?
also, why didn't we touch filmHtml?
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 59
import { __, compose, filter, gte, map, propSatisfies } from 'ramda'
const filmHtml = film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
const filmsHtml =
map(filmHtml)
const hasHighRating =
propSatisfies(gte(__, 8.8), 'rating')
const onlyHighRatings =
filter(hasHighRating)
const bestFilmsHtml =
compose(filmsHtml, onlyHighRatings)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 60
extract isHighRating
import { __, compose, filter, gte, map, propSatisfies } from 'ramda'
const filmHtml = film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
const filmsHtml =
map(filmHtml)
const isHighRating =
gte(__, 8.8)
const hasHighRating =
propSatisfies(isHighRating, 'rating')
const onlyHighRatings =
filter(hasHighRating)
const bestFilmsHtml =
compose(filmsHtml, onlyHighRatings)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 61
final product
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 62
import { __, compose, filter, gte, map, propSatisfies } from 'ramda'
const filmHtml = film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
const filmsHtml =
map(filmHtml)
const isHighRating =
gte(__, 8.8)
const hasHighRating =
propSatisfies(isHighRating, 'rating')
const onlyHighRatings =
filter(hasHighRating)
const bestFilmsHtml =
compose(filmsHtml, onlyHighRatings)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 63
quick examples from other
languages (λ)
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 64
haskell (λ)
loadLines :: FilePath -> IO [String]
loadLines =
fmap lines . readFile
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 65
λ calculus
https://www.lambda-bound.com/book/lambdacalc/node21.html
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 66
a path to point-free JS
Ask yourself,
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 67
a path to point-free JS
Ask yourself,
1. "Do I need these variables in my function?"
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 67
a path to point-free JS
Ask yourself,
1. "Do I need these variables in my function?"
2. "Are my data transformations associative?"
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 67
a path to point-free JS
Ask yourself,
1. "Do I need these variables in my function?"
2. "Are my data transformations associative?"
3. "Am I chaining .then().then().then()s?" or sequencing
effectful expressions? (see composeWith(then))
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 67
a path to point-free JS
Ask yourself,
1. "Do I need these variables in my function?"
2. "Are my data transformations associative?"
3. "Am I chaining .then().then().then()s?" or sequencing
effectful expressions? (see composeWith(then))
4. "Is my function doing more than 1 thing? Should it? Can I reuse
the other things it's doing?"
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 67
a path to point-free JS
At work,
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
a path to point-free JS
At work,
1. write your own compose function and start using it
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
a path to point-free JS
At work,
1. write your own compose function and start using it
2. pull in ramda.js or crocks.js and use a few functions
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
a path to point-free JS
At work,
1. write your own compose function and start using it
2. pull in ramda.js or crocks.js and use a few functions
3. use function naming and types (or pseudo-types) to tell a story
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
a path to point-free JS
At work,
1. write your own compose function and start using it
2. pull in ramda.js or crocks.js and use a few functions
3. use function naming and types (or pseudo-types) to tell a story
4. prefer composition to inheritance
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
a path to point-free JS
At work,
1. write your own compose function and start using it
2. pull in ramda.js or crocks.js and use a few functions
3. use function naming and types (or pseudo-types) to tell a story
4. prefer composition to inheritance
5. try to break down complexity into dumbed-down simplicity ("Don't Make Me Think")
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
a path to point-free JS
At work,
1. write your own compose function and start using it
2. pull in ramda.js or crocks.js and use a few functions
3. use function naming and types (or pseudo-types) to tell a story
4. prefer composition to inheritance
5. try to break down complexity into dumbed-down simplicity ("Don't Make Me Think")
6. genericize as many things as you can so that the only single-use functions are
those directly related to business logic
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
where can i learn more?
• ramda.js
• Functional Programming in JavaScript with Ramda.js
• crocks.js
• Professor Frisby's Mostly Adequate Guide to Functional
Programming
• Point-Free or Die: Tacit Programming in Haskell and Beyond
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 69
where can i learn more?
rwp.im
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 70
A Path to Point-Free JS
web :: rwp.im
github :: rpearce
email :: me@robertwpearce.com
tweeter :: @RobertWPearce
@RobertWPearce | rwp.im | 2019-10-04 charleston.js 71

A Path to Point-Free JavaScript

  • 1.
    A Path toPoint-Free JS web :: rwp.im github :: rpearce email :: me@robertwpearce.com tweeter :: @RobertWPearce @RobertWPearce | rwp.im | 2019-10-04 charleston.js 1
  • 2.
    about me Hi, I'mRobert @RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
  • 3.
    about me Hi, I'mRobert • My kiwi wife & I have recently moved back from New Zealand @RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
  • 4.
    about me Hi, I'mRobert • My kiwi wife & I have recently moved back from New Zealand • Started out at Jack Russell Software w/ Tom Wilson in 2011 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
  • 5.
    about me Hi, I'mRobert • My kiwi wife & I have recently moved back from New Zealand • Started out at Jack Russell Software w/ Tom Wilson in 2011 • Currently with Articulate, the e-learning software company @RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
  • 6.
    about me Hi, I'mRobert • My kiwi wife & I have recently moved back from New Zealand • Started out at Jack Russell Software w/ Tom Wilson in 2011 • Currently with Articulate, the e-learning software company • Work with FP in JS all day @RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
  • 7.
    about me Hi, I'mRobert • My kiwi wife & I have recently moved back from New Zealand • Started out at Jack Russell Software w/ Tom Wilson in 2011 • Currently with Articulate, the e-learning software company • Work with FP in JS all day • Learn Haskell (λ) and other FP in what little free time I have @RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
  • 8.
    about me Hi, I'mRobert • My kiwi wife & I have recently moved back from New Zealand • Started out at Jack Russell Software w/ Tom Wilson in 2011 • Currently with Articulate, the e-learning software company • Work with FP in JS all day • Learn Haskell (λ) and other FP in what little free time I have • Pretend to know what I'm doing @RobertWPearce | rwp.im | 2019-10-04 charleston.js 2
  • 9.
    Question: what are wegoing to do? @RobertWPearce | rwp.im | 2019-10-04 charleston.js 3
  • 10.
    Answer: ultimately attempt tosimplify this code // bestFilmsHtml :: [Film] -> [Html] const bestFilmsHtml = films => { const bestFilms = films.filter(film => film.rating >= 8.8) const filmsHtml = bestFilms.map(film => `<div>${film.title}, <strong>${film.rating}</strong></div>` ) return filmsHtml } @RobertWPearce | rwp.im | 2019-10-04 charleston.js 4
  • 11.
    Answer: which takes thisdata... const films = [ { title: 'The Empire Strikes Back', rating: 8.8 }, { title: 'Pulp Fiction', rating: 8.9 }, { title: 'The Deer Hunter', rating: 8.2 }, { title: 'The Lion King', rating: 8.5 } ] @RobertWPearce | rwp.im | 2019-10-04 charleston.js 5
  • 12.
    Answer: ...and returns this [ "<div>TheEmpire Strikes Back, <strong>8.8</strong></div>", "<div>Pulp Fiction, <strong>8.9</strong></div>" ] @RobertWPearce | rwp.im | 2019-10-04 charleston.js 6
  • 13.
    Question: how are wegoing to do that? @RobertWPearce | rwp.im | 2019-10-04 charleston.js 7
  • 14.
    Answer: @RobertWPearce | rwp.im| 2019-10-04 charleston.js 8
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
    Answer: • ! currying! • " composition! • # $ eta-conversion! • % ramda.js! • ...thisis our Friday night ✨ @RobertWPearce | rwp.im | 2019-10-04 charleston.js 8
  • 20.
    Question: what does point-freemean? @RobertWPearce | rwp.im | 2019-10-04 charleston.js 9
  • 21.
    Tacit programming, alsocalled point-free style, is a programming paradigm in which function definitions do not identify the arguments (or "points") on which they operate. Instead the definitions merely compose other functions, among which are combinators that manipulate the arguments. — https://en.wikipedia.org/wiki/Tacit_programming @RobertWPearce | rwp.im | 2019-10-04 charleston.js 10
  • 22.
    Combinatory logic isa notation to eliminate the need for quantified variables in mathematical logic [...] based on combinators which were introduced by Schönfinkel in 1920 with the idea of providing an analogous way to build up functions - and to remove any mention of variables - particularly in predicate logic. A combinator is a higher-order function that uses only function application and earlier defined combinators to define a result from its arguments. — https://en.wikipedia.org/wiki/Combinatory_logic @RobertWPearce | rwp.im | 2019-10-04 charleston.js 11
  • 23.
    ...there are otheropinions @RobertWPearce | rwp.im | 2019-10-04 charleston.js 12
  • 24.
    The lack ofargument naming gives point-free style a reputation of being unnecessarily obscure, hence the epithet "pointless style". — https://en.wikipedia.org/wiki/Tacit_programming @RobertWPearce | rwp.im | 2019-10-04 charleston.js 13
  • 25.
    some may disagree "Point-Freeor Die: Tacit Programming in Haskell and Beyond" https://www.youtube.com/watch?v=seVSlKazsNk @RobertWPearce | rwp.im | 2019-10-04 charleston.js 14
  • 26.
    Question: what does itlook like? @RobertWPearce | rwp.im | 2019-10-04 charleston.js 15
  • 27.
    (do we careabout the implementation?) (say yes) const films = [ { title: 'The Empire Strikes Back', rating: 8.8 }, { title: 'Pulp Fiction', rating: 8.9 }, { title: 'The Deer Hunter', rating: 8.2 }, { title: 'The Lion King', rating: 8.5 } ] bestFilmsHtml(films) // => [ // "<div>The Empire Strikes Back, <strong>8.8</strong></div>", // "<div>Pulp Fiction, <strong>8.9</strong></div>" // ] @RobertWPearce | rwp.im | 2019-10-04 charleston.js 16
  • 28.
    (do we careabout the implementation?) (say yes) const films = [ { title: 'The Empire Strikes Back', rating: 8.8 }, { title: 'Pulp Fiction', rating: 8.9 }, { title: 'The Deer Hunter', rating: 8.2 }, { title: 'The Lion King', rating: 8.5 } ] bestFilmsHtml(films) // => [ // "<div>The Empire Strikes Back, <strong>8.8</strong></div>", // "<div>Pulp Fiction, <strong>8.9</strong></div>" // ] @RobertWPearce | rwp.im | 2019-10-04 charleston.js 16
  • 29.
    bestFilmsHtml // bestFilmsHtml ::[Film] -> [Html] const bestFilmsHtml = compose(filmsHtml, onlyHighRatings) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 17
  • 30.
    onlyHighRatings // onlyHighRatings ::[Film] -> [Film] const onlyHighRatings = filter(hasHighRating) // bestFilmsHtml :: [Film] -> [Html] const bestFilmsHtml = compose(filmsHtml, onlyHighRatings) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 18
  • 31.
    hasHighRating // hasHighRating ::Film -> Bool const hasHighRating = propSatisfies(gte(__, 8.8), 'rating') // onlyHighRatings :: [Film] -> [Film] const onlyHighRatings = filter(hasHighRating) // bestFilmsHtml :: [Film] -> [Html] const bestFilmsHtml = compose(filmsHtml, onlyHighRatings) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 19
  • 32.
    filmsHtml // filmsHtml ::[Film] -> [Html] const filmsHtml = map(filmHtml) // hasHighRating :: Film -> Bool const hasHighRating = propSatisfies(gte(__, 8.8), 'rating') // onlyHighRatings :: [Film] -> [Film] const onlyHighRatings = filter(hasHighRating) // bestFilmsHtml :: [Film] -> [Html] const bestFilmsHtml = compose(filmsHtml, onlyHighRatings) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 20
  • 33.
    filmHtml (notice anydifference?) // filmHtml :: Film -> Html const filmHtml = film => `<div>${film.title}, <strong>${film.rating}</strong></div>` // filmsHtml :: [Film] -> [Html] const filmsHtml = map(filmHtml) // hasHighRating :: Film -> Bool const hasHighRating = propSatisfies(gte(__, 8.8), 'rating') // onlyHighRatings :: [Film] -> [Film] const onlyHighRatings = filter(hasHighRating) // bestFilmsHtml :: [Film] -> [Html] const bestFilmsHtml = compose(filmsHtml, onlyHighRatings) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 21
  • 34.
    filmHtml (notice anydifference?) // filmHtml :: Film -> Html const filmHtml = film => `<div>${film.title}, <strong>${film.rating}</strong></div>` // filmsHtml :: [Film] -> [Html] const filmsHtml = map(filmHtml) // hasHighRating :: Film -> Bool const hasHighRating = propSatisfies(gte(__, 8.8), 'rating') // onlyHighRatings :: [Film] -> [Film] const onlyHighRatings = filter(hasHighRating) // bestFilmsHtml :: [Film] -> [Html] const bestFilmsHtml = compose(filmsHtml, onlyHighRatings) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 21
  • 35.
    ! buckle up it's timefor prerequisites @RobertWPearce | rwp.im | 2019-10-04 charleston.js 22
  • 36.
    currying ! and partial application constadd = (a, b) => b + a const add2 = add.bind(null, 2) add2(3) // 5 add2(5) // 7 const add = a => b => b + a const add3 = add(3) add3(2) // 5 add3(4) // 7 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
  • 37.
    currying ! and partial application constadd = (a, b) => b + a const add2 = add.bind(null, 2) add2(3) // 5 add2(5) // 7 const add = a => b => b + a const add3 = add(3) add3(2) // 5 add3(4) // 7 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
  • 38.
    currying ! and partial application constadd = (a, b) => b + a const add2 = add.bind(null, 2) add2(3) // 5 add2(5) // 7 const add = a => b => b + a const add3 = add(3) add3(2) // 5 add3(4) // 7 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
  • 39.
    currying ! and partial application constadd = (a, b) => b + a const add2 = add.bind(null, 2) add2(3) // 5 add2(5) // 7 const add = a => b => b + a const add3 = add(3) add3(2) // 5 add3(4) // 7 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
  • 40.
    currying ! and partial application constadd = (a, b) => b + a const add2 = add.bind(null, 2) add2(3) // 5 add2(5) // 7 const add = a => b => b + a const add3 = add(3) add3(2) // 5 add3(4) // 7 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
  • 41.
    currying ! and partial application constadd = (a, b) => b + a const add2 = add.bind(null, 2) add2(3) // 5 add2(5) // 7 const add = a => b => b + a const add3 = add(3) add3(2) // 5 add3(4) // 7 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
  • 42.
    currying ! and partial application constadd = (a, b) => b + a const add2 = add.bind(null, 2) add2(3) // 5 add2(5) // 7 const add = a => b => b + a const add3 = add(3) add3(2) // 5 add3(4) // 7 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
  • 43.
    currying ! and partial application constadd = (a, b) => b + a const add2 = add.bind(null, 2) add2(3) // 5 add2(5) // 7 const add = a => b => b + a const add3 = add(3) add3(2) // 5 add3(4) // 7 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 23
  • 44.
    In mathematics andcomputer science, currying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument. — https://en.wikipedia.org/wiki/Currying @RobertWPearce | rwp.im | 2019-10-04 charleston.js 24
  • 45.
    currying ! and partial application constadd = a => b => b + a // is equivalent to function add(a) { return function(b) { return b + a } } // but... add(2)(3) // 5 add(2, 3) // function add() ... ( °□° @RobertWPearce | rwp.im | 2019-10-04 charleston.js 25
  • 46.
    currying ! and partial application constadd = a => b => b + a // is equivalent to function add(a) { return function(b) { return b + a } } // but... add(2)(3) // 5 add(2, 3) // function add() ... ( °□° @RobertWPearce | rwp.im | 2019-10-04 charleston.js 25
  • 47.
    currying ! and partial application constadd = a => b => b + a // is equivalent to function add(a) { return function(b) { return b + a } } // but... add(2)(3) // 5 add(2, 3) // function add() ... ( °□° @RobertWPearce | rwp.im | 2019-10-04 charleston.js 25
  • 48.
    currying ! and partial application constadd = a => b => b + a // is equivalent to function add(a) { return function(b) { return b + a } } // but... add(2)(3) // 5 add(2, 3) // function add() ... ( °□° @RobertWPearce | rwp.im | 2019-10-04 charleston.js 25
  • 49.
    currying ! and partial application constadd = a => b => b + a // is equivalent to function add(a) { return function(b) { return b + a } } // but... add(2)(3) // 5 add(2, 3) // function add() ... ( °□° @RobertWPearce | rwp.im | 2019-10-04 charleston.js 25
  • 50.
    currying ! and partial application constadd = a => b => b + a // is equivalent to function add(a) { return function(b) { return b + a } } // but... add(2)(3) // 5 add(2, 3) // function add() ... ( °□° @RobertWPearce | rwp.im | 2019-10-04 charleston.js 25
  • 51.
    autocurrying // add.js import {curry } from 'ramda' const add = (a, b) => b + a export default curry(add) // some other file import add from './add' add(2, 3) // 5 add(2)(3) // 5 add()(2)(3) // 5 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
  • 52.
    autocurrying // add.js import {curry } from 'ramda' const add = (a, b) => b + a export default curry(add) // some other file import add from './add' add(2, 3) // 5 add(2)(3) // 5 add()(2)(3) // 5 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
  • 53.
    autocurrying // add.js import {curry } from 'ramda' const add = (a, b) => b + a export default curry(add) // some other file import add from './add' add(2, 3) // 5 add(2)(3) // 5 add()(2)(3) // 5 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
  • 54.
    autocurrying // add.js import {curry } from 'ramda' const add = (a, b) => b + a export default curry(add) // some other file import add from './add' add(2, 3) // 5 add(2)(3) // 5 add()(2)(3) // 5 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
  • 55.
    autocurrying // add.js import {curry } from 'ramda' const add = (a, b) => b + a export default curry(add) // some other file import add from './add' add(2, 3) // 5 add(2)(3) // 5 add()(2)(3) // 5 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
  • 56.
    autocurrying // add.js import {curry } from 'ramda' const add = (a, b) => b + a export default curry(add) // some other file import add from './add' add(2, 3) // 5 add(2)(3) // 5 add()(2)(3) // 5 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
  • 57.
    autocurrying // add.js import {curry } from 'ramda' const add = (a, b) => b + a export default curry(add) // some other file import add from './add' add(2, 3) // 5 add(2)(3) // 5 add()(2)(3) // 5 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
  • 58.
    autocurrying // add.js import {curry } from 'ramda' const add = (a, b) => b + a export default curry(add) // some other file import add from './add' add(2, 3) // 5 add(2)(3) // 5 add()(2)(3) // 5 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
  • 59.
    autocurrying // add.js import {curry } from 'ramda' const add = (a, b) => b + a export default curry(add) // some other file import add from './add' add(2, 3) // 5 add(2)(3) // 5 add()(2)(3) // 5 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
  • 60.
    autocurrying // add.js import {curry } from 'ramda' const add = (a, b) => b + a export default curry(add) // some other file import add from './add' add(2, 3) // 5 add(2)(3) // 5 add()(2)(3) // 5 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 26
  • 61.
  • 62.
    sweet! who caresabout currying? currying helps with composition! @RobertWPearce | rwp.im | 2019-10-04 charleston.js 28
  • 63.
    composition import add from'./add' import mult from './mult' const add2 = add(2) // partial application const mult3 = mult(3) // partial application mult3(add2(10)) // composition & full application // returns 36 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 29
  • 64.
    composition import add from'./add' import mult from './mult' const add2 = add(2) // partial application const mult3 = mult(3) // partial application mult3(add2(10)) // composition & full application // returns 36 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 29
  • 65.
    composition import add from'./add' import mult from './mult' const add2 = add(2) // partial application const mult3 = mult(3) // partial application mult3(add2(10)) // composition & full application // returns 36 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 29
  • 66.
    how could wereuse this composition? @RobertWPearce | rwp.im | 2019-10-04 charleston.js 30
  • 67.
    mult3(add2(10)) @RobertWPearce | rwp.im| 2019-10-04 charleston.js 31
  • 68.
    // mult3(add2(10)) const compose= (f, g) => x => f(g(x)) const add2mult3 = compose(mult3, add2) add2mult3(10) // 36 add2mult3(42) // 132 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 32
  • 69.
    // mult3(add2(10)) const compose= (f, g) => x => f(g(x)) const add2mult3 = compose(mult3, add2) add2mult3(10) // 36 add2mult3(42) // 132 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 32
  • 70.
    // mult3(add2(10)) const compose= (f, g) => x => f(g(x)) const add2mult3 = compose(mult3, add2) add2mult3(10) // 36 add2mult3(42) // 132 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 32
  • 71.
    // mult3(add2(10)) const compose= (f, g) => x => f(g(x)) const add2mult3 = compose(mult3, add2) add2mult3(10) // 36 add2mult3(42) // 132 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 32
  • 72.
    // mult3(add2(10)) const compose= (f, g) => x => f(g(x)) const add2mult3 = compose(mult3, add2) add2mult3(10) // 36 add2mult3(42) // 132 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 32
  • 73.
    // mult3(add2(10)) const compose= (f, g) => x => f(g(x)) const add2mult3 = compose(mult3, add2) add2mult3(10) // 36 add2mult3(42) // 132 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 32
  • 74.
    our compose isn'tvariadic, though let's reach for a tool @RobertWPearce | rwp.im | 2019-10-04 charleston.js 33
  • 75.
    composition import { compose} from 'ramda' import add from './add' import mult from './mult' const add2 = add(2) const mult3 = mult(3) const add2mult3Repeated = compose(mult3, add2, mult3, add2) add2mult3Repeated(10) // 114 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 34
  • 76.
    composition import { compose} from 'ramda' import add from './add' import mult from './mult' const add2 = add(2) const mult3 = mult(3) const add2mult3Repeated = compose(mult3, add2, mult3, add2) add2mult3Repeated(10) // 114 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 34
  • 77.
    composition import { compose} from 'ramda' import add from './add' import mult from './mult' const add2 = add(2) const mult3 = mult(3) const add2mult3Repeated = compose(mult3, add2, mult3, add2) add2mult3Repeated(10) // 114 @RobertWPearce | rwp.im | 2019-10-04 charleston.js 34
  • 78.
    reading composition compose(f, g)(x)≡ f(g(x)) Say it with me, "f after g" @RobertWPearce | rwp.im | 2019-10-04 charleston.js 35
  • 79.
  • 80.
    eta-conversion @RobertWPearce | rwp.im| 2019-10-04 charleston.js 37
  • 81.
    eta-conversion • "An etaconversion (also written η-conversion) is adding or dropping of abstraction over a function." https://wiki.haskell.org/Eta_conversion @RobertWPearce | rwp.im | 2019-10-04 charleston.js 37
  • 82.
    eta-conversion • "An etaconversion (also written η-conversion) is adding or dropping of abstraction over a function." https://wiki.haskell.org/Eta_conversion • eta-abstraction ! @RobertWPearce | rwp.im | 2019-10-04 charleston.js 37
  • 83.
    eta-conversion • "An etaconversion (also written η-conversion) is adding or dropping of abstraction over a function." https://wiki.haskell.org/Eta_conversion • eta-abstraction ! • eta-reduction " @RobertWPearce | rwp.im | 2019-10-04 charleston.js 37
  • 84.
    eta-abstraction Remember this? const add2mult3= compose(mult3, add2) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 38
  • 85.
    eta-abstraction Remember this? const add2mult3= compose(mult3, add2) Here's how we would perform an eta-abstraction: const add2mult3 = n => compose(mult3, add2)(n) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 39
  • 86.
    eta-reduction Let's take ourrecent eta-abstraction, const add2mult3 = n => compose(mult3, add2)(n) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 40
  • 87.
    eta-reduction Let's take ourrecent eta-abstraction, const add2mult3 = n => compose(mult3, add2)(n) and perform an eta-reduction: const add2mult3 = compose(mult3, add2) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 41
  • 88.
    ! we covered theprerequisites! @RobertWPearce | rwp.im | 2019-10-04 charleston.js 42
  • 89.
    let's talk aboutramda.js @RobertWPearce | rwp.im | 2019-10-04 charleston.js 43
  • 90.
    let's talk aboutramda.js • functional library @RobertWPearce | rwp.im | 2019-10-04 charleston.js 43
  • 91.
    let's talk aboutramda.js • functional library • easy to make functional pipelines @RobertWPearce | rwp.im | 2019-10-04 charleston.js 43
  • 92.
    let's talk aboutramda.js • functional library • easy to make functional pipelines • never mutates data @RobertWPearce | rwp.im | 2019-10-04 charleston.js 43
  • 93.
    let's talk aboutramda.js • functional library • easy to make functional pipelines • never mutates data • all functions are automatically curried @RobertWPearce | rwp.im | 2019-10-04 charleston.js 43
  • 94.
    let's talk aboutramda.js • functional library • easy to make functional pipelines • never mutates data • all functions are automatically curried • parameters to functions make it convenient for currying; data to be operated on generally comes last, for example @RobertWPearce | rwp.im | 2019-10-04 charleston.js 43
  • 95.
    useful ramda functionsfor us right now @RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
  • 96.
    useful ramda functionsfor us right now • curry :: (* → a) → (* → a) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
  • 97.
    useful ramda functionsfor us right now • curry :: (* → a) → (* → a) • compose :: ((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b, …, n) → z) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
  • 98.
    useful ramda functionsfor us right now • curry :: (* → a) → (* → a) • compose :: ((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b, …, n) → z) • filter :: Filterable f => (a → Boolean) → f a → f a @RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
  • 99.
    useful ramda functionsfor us right now • curry :: (* → a) → (* → a) • compose :: ((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b, …, n) → z) • filter :: Filterable f => (a → Boolean) → f a → f a • map :: Functor f => (a → b) → f a → f b @RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
  • 100.
    useful ramda functionsfor us right now • curry :: (* → a) → (* → a) • compose :: ((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b, …, n) → z) • filter :: Filterable f => (a → Boolean) → f a → f a • map :: Functor f => (a → b) → f a → f b • propSatisfies :: (a → Boolean) → String → {String: a} → Boolean @RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
  • 101.
    useful ramda functionsfor us right now • curry :: (* → a) → (* → a) • compose :: ((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b, …, n) → z) • filter :: Filterable f => (a → Boolean) → f a → f a • map :: Functor f => (a → b) → f a → f b • propSatisfies :: (a → Boolean) → String → {String: a} → Boolean • gte :: Ord a => a → a → Boolean @RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
  • 102.
    useful ramda functionsfor us right now • curry :: (* → a) → (* → a) • compose :: ((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b, …, n) → z) • filter :: Filterable f => (a → Boolean) → f a → f a • map :: Functor f => (a → b) → f a → f b • propSatisfies :: (a → Boolean) → String → {String: a} → Boolean • gte :: Ord a => a → a → Boolean • __ (used to specify gaps within curried functions so that we can do partial application regardless of argument order) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 44
  • 103.
    our original codeto refactor // bestFilmsHtml :: [Film] -> [Html] const bestFilmsHtml = films => { const bestFilms = films.filter(film => film.rating >= 8.8) const filmsHtml = bestFilms.map(film => `<div>${film.title}, <strong>${film.rating}</strong></div>` ) return filmsHtml } @RobertWPearce | rwp.im | 2019-10-04 charleston.js 45
  • 104.
    we got this @RobertWPearce| rwp.im | 2019-10-04 charleston.js 46
  • 105.
    implicit return b/cvariables weren't needed const bestFilmsHtml = films => films .filter(film => film.rating >= 8.8) .map(film => `<div>${film.title}, <strong>${film.rating}</strong></div>` ) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 47
  • 106.
    extract hasHighRating const hasHighRating= film => film.rating >= 8.8 const bestFilmsHtml = films => films .filter(hasHighRating) .map(film => `<div>${film.title}, <strong>${film.rating}</strong></div>` ) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 48
  • 107.
    refactor hasHighRating import {__, gte, propSatisfies } from 'ramda' const hasHighRating = film => propSatisfies(gte(__, 8.8), 'rating')(film) const bestFilmsHtml = films => films .filter(hasHighRating) .map(film => `<div>${film.title}, <strong>${film.rating}</strong></div>` ) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 49
  • 108.
    eta-reduce hasHighRating import {__, gte, propSatisfies } from 'ramda' const hasHighRating = propSatisfies(gte(__, 8.8), 'rating') const bestFilmsHtml = films => films .filter(hasHighRating) .map(film => `<div>${film.title}, <strong>${film.rating}</strong></div>` ) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 50
  • 109.
    extract filmHtml import {__, gte, propSatisfies } from 'ramda' const filmHtml = film => `<div>${film.title}, <strong>${film.rating}</strong></div>` const hasHighRating = propSatisfies(gte(__, 8.8), 'rating') const bestFilmsHtml = films => films .filter(hasHighRating) .map(filmHtml) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 51
  • 110.
    convert bestFilmsHtml touse compose import { __, compose, filter, gte, map, propSatisfies } from 'ramda' const filmHtml = film => `<div>${film.title}, <strong>${film.rating}</strong></div>` const hasHighRating = propSatisfies(gte(__, 8.8), 'rating') const bestFilmsHtml = films => compose(map(filmHtml), filter(hasHighRating))(films) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 52
  • 111.
    eta-reduce bestFilmsHtml import {__, compose, filter, gte, map, propSatisfies } from 'ramda' const filmHtml = film => `<div>${film.title}, <strong>${film.rating}</strong></div>` const hasHighRating = propSatisfies(gte(__, 8.8), 'rating') const bestFilmsHtml = compose(map(filmHtml), filter(hasHighRating)) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 53
  • 112.
    we could stophere... @RobertWPearce | rwp.im | 2019-10-04 charleston.js 54
  • 113.
    import { __,compose, filter, gte, map, propSatisfies } from 'ramda' const filmHtml = film => `<div>${film.title}, <strong>${film.rating}</strong></div>` const hasHighRating = propSatisfies(gte(__, 8.8), 'rating') const bestFilmsHtml = compose(map(filmHtml), filter(hasHighRating)) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 55
  • 114.
    ...but let's gofor clarity & reuse @RobertWPearce | rwp.im | 2019-10-04 charleston.js 56
  • 115.
    extract onlyHighRatings import {__, compose, filter, gte, map, propSatisfies } from 'ramda' const filmHtml = film => `<div>${film.title}, <strong>${film.rating}</strong></div>` const hasHighRating = propSatisfies(gte(__, 8.8), 'rating') const onlyHighRatings = filter(hasHighRating) const bestFilmsHtml = compose(map(filmHtml), onlyHighRatings) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 57
  • 116.
    extract filmsHtml import {__, compose, filter, gte, map, propSatisfies } from 'ramda' const filmHtml = film => `<div>${film.title}, <strong>${film.rating}</strong></div>` const filmsHtml = map(filmHtml) const hasHighRating = propSatisfies(gte(__, 8.8), 'rating') const onlyHighRatings = filter(hasHighRating) const bestFilmsHtml = compose(filmsHtml, onlyHighRatings) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 58
  • 117.
    did we missone? does it matter? also, why didn't we touch filmHtml? @RobertWPearce | rwp.im | 2019-10-04 charleston.js 59
  • 118.
    import { __,compose, filter, gte, map, propSatisfies } from 'ramda' const filmHtml = film => `<div>${film.title}, <strong>${film.rating}</strong></div>` const filmsHtml = map(filmHtml) const hasHighRating = propSatisfies(gte(__, 8.8), 'rating') const onlyHighRatings = filter(hasHighRating) const bestFilmsHtml = compose(filmsHtml, onlyHighRatings) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 60
  • 119.
    extract isHighRating import {__, compose, filter, gte, map, propSatisfies } from 'ramda' const filmHtml = film => `<div>${film.title}, <strong>${film.rating}</strong></div>` const filmsHtml = map(filmHtml) const isHighRating = gte(__, 8.8) const hasHighRating = propSatisfies(isHighRating, 'rating') const onlyHighRatings = filter(hasHighRating) const bestFilmsHtml = compose(filmsHtml, onlyHighRatings) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 61
  • 120.
    final product @RobertWPearce |rwp.im | 2019-10-04 charleston.js 62
  • 121.
    import { __,compose, filter, gte, map, propSatisfies } from 'ramda' const filmHtml = film => `<div>${film.title}, <strong>${film.rating}</strong></div>` const filmsHtml = map(filmHtml) const isHighRating = gte(__, 8.8) const hasHighRating = propSatisfies(isHighRating, 'rating') const onlyHighRatings = filter(hasHighRating) const bestFilmsHtml = compose(filmsHtml, onlyHighRatings) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 63
  • 122.
    quick examples fromother languages (λ) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 64
  • 123.
    haskell (λ) loadLines ::FilePath -> IO [String] loadLines = fmap lines . readFile @RobertWPearce | rwp.im | 2019-10-04 charleston.js 65
  • 124.
  • 125.
    a path topoint-free JS Ask yourself, @RobertWPearce | rwp.im | 2019-10-04 charleston.js 67
  • 126.
    a path topoint-free JS Ask yourself, 1. "Do I need these variables in my function?" @RobertWPearce | rwp.im | 2019-10-04 charleston.js 67
  • 127.
    a path topoint-free JS Ask yourself, 1. "Do I need these variables in my function?" 2. "Are my data transformations associative?" @RobertWPearce | rwp.im | 2019-10-04 charleston.js 67
  • 128.
    a path topoint-free JS Ask yourself, 1. "Do I need these variables in my function?" 2. "Are my data transformations associative?" 3. "Am I chaining .then().then().then()s?" or sequencing effectful expressions? (see composeWith(then)) @RobertWPearce | rwp.im | 2019-10-04 charleston.js 67
  • 129.
    a path topoint-free JS Ask yourself, 1. "Do I need these variables in my function?" 2. "Are my data transformations associative?" 3. "Am I chaining .then().then().then()s?" or sequencing effectful expressions? (see composeWith(then)) 4. "Is my function doing more than 1 thing? Should it? Can I reuse the other things it's doing?" @RobertWPearce | rwp.im | 2019-10-04 charleston.js 67
  • 130.
    a path topoint-free JS At work, @RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
  • 131.
    a path topoint-free JS At work, 1. write your own compose function and start using it @RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
  • 132.
    a path topoint-free JS At work, 1. write your own compose function and start using it 2. pull in ramda.js or crocks.js and use a few functions @RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
  • 133.
    a path topoint-free JS At work, 1. write your own compose function and start using it 2. pull in ramda.js or crocks.js and use a few functions 3. use function naming and types (or pseudo-types) to tell a story @RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
  • 134.
    a path topoint-free JS At work, 1. write your own compose function and start using it 2. pull in ramda.js or crocks.js and use a few functions 3. use function naming and types (or pseudo-types) to tell a story 4. prefer composition to inheritance @RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
  • 135.
    a path topoint-free JS At work, 1. write your own compose function and start using it 2. pull in ramda.js or crocks.js and use a few functions 3. use function naming and types (or pseudo-types) to tell a story 4. prefer composition to inheritance 5. try to break down complexity into dumbed-down simplicity ("Don't Make Me Think") @RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
  • 136.
    a path topoint-free JS At work, 1. write your own compose function and start using it 2. pull in ramda.js or crocks.js and use a few functions 3. use function naming and types (or pseudo-types) to tell a story 4. prefer composition to inheritance 5. try to break down complexity into dumbed-down simplicity ("Don't Make Me Think") 6. genericize as many things as you can so that the only single-use functions are those directly related to business logic @RobertWPearce | rwp.im | 2019-10-04 charleston.js 68
  • 137.
    where can ilearn more? • ramda.js • Functional Programming in JavaScript with Ramda.js • crocks.js • Professor Frisby's Mostly Adequate Guide to Functional Programming • Point-Free or Die: Tacit Programming in Haskell and Beyond @RobertWPearce | rwp.im | 2019-10-04 charleston.js 69
  • 138.
    where can ilearn more? rwp.im @RobertWPearce | rwp.im | 2019-10-04 charleston.js 70
  • 139.
    A Path toPoint-Free JS web :: rwp.im github :: rpearce email :: me@robertwpearce.com tweeter :: @RobertWPearce @RobertWPearce | rwp.im | 2019-10-04 charleston.js 71