Functional Programming Deep Dive
FP Pros/Cons
Pros
Cons
Easy to create functions from smaller functions
Lots of helpers(?)
Encourages readable code
Easy to create unreadable code
Encourages good programming practice
Relies on programmer discipline
High Reusability
Declarative Code
Example of FP Power
const input = {
cities: [{
name: 'Calgary',
isTopCity: false,
population: 50000000
}, {
name: 'Vancouver',
isTopCity: true,
population: 5000000
}, {
name: 'Toronto',
isTopCity: true,
population: 400000
}]
}
const addProps = R.curry((spec, data) => R.ap(R.merge, R.applySpec(spec))(data))
const deserializeObject = R.pipe(
R.applySpec({
topCities: R.compose(
R.filter(R.propEq('isTopCity', true)),
R.prop('cities')
)
}),
addProps({
totalTopPopulation: R.compose(
R.sum,
R.pluck('population'),
R.prop('topCities')
)
})
)
See it works!
What was wrong with it?
const input = {
cities: [{
name: 'Calgary',
isTopCity: false,
population: 50000000
}, {
name: 'Vancouver',
isTopCity: true,
population: 5000000
}, {
name: 'Toronto',
isTopCity: true,
population: 400000
}]
}
const addProps = R.curry((spec, data) => R.ap(R.merge, R.applySpec(spec))(data))
const isTopCity = R.propEq('isTopCity', true)
const filterTopCities = R.filter(isTopCity)
const cities = R.prop('cities')
const topCities = R.prop('topCities')
const citiesTopCities = R.compose(filterTopCities, cities)
const cityPopulations = R.pluck('population')
const totalTopPopulation = R.compose(R.sum, cityPopulations, topCities)
const topCitiesSpec = R.applySpec({topCities: citiesTopCities})
const addTotalTopPopulations = addProps({totalTopPopulation})
const deserializeObject = R.pipe(topCitiesSpec, addTotalTopPopulations)
So many helpers @_@
- Is the code cleaner?
- Can the code be reused
Fragility
What happens when there are no cities at all?
const input = {}
deserializeObject(input)
Uncaught TypeError:
Cannot read property 'filter' of undefined
How can we solve
- Null-check all the things
- Functional containers
Null-Checks
const isNotNil = R.complement(R.isNil)
const addProps = R.curry((spec, data) => R.ap(R.merge, R.applySpec(spec))(data))
const isTopCity = R.propEq('isTopCity', true)
const filterTopCities = R.when(isNotNil, R.filter(isTopCity))
const cities = R.prop('cities')
const topCities = R.prop('topCities')
const citiesTopCities = R.compose(filterTopCities, cities)
const cityPopulations = R.when(isNotNil, R.pluck('population'))
const totalTopPopulation = R.compose(R.when(isNotNil, R.sum), cityPopulations, topCities)
const topCitiesSpec = R.when(isNotNil, R.applySpec({topCities: citiesTopCities}))
const addTotalTopPopulations = R.when(isNotNil, addProps({totalTopPopulation}))
Functional Containers
const addProps = R.curry((spec, data) => R.ap(R.merge, R.applySpec(spec))(data))
const isTopCity = R.propEq('isTopCity', true)
const filterTopCities = S.filter(isTopCity)
const cities = S.prop('cities')
const topCities = S.prop('topCities')
const citiesTopCities = R.compose(filterTopCities, cities)
const cityPopulations = S.pluck('population')
const totalTopPopulation = R.compose(S.sum, cityPopulations, topCities)
const topCitiesSpec = R.applySpec({topCities: citiesTopCities})
const addTotalTopPopulations = addProps({totalTopPopulation})
const deserializeTopCity = R.pipe(
S.toMaybe,
R.map(R.compose(topCitiesSpec),
R.map(addTotalTopPopulations),
R.prop('value') // get Value from maybe
)
Introducing Functional Containers and Monadic Types
- Wrap values
- Allow for transforms
- Fault tolerant
Just(3).map(R.inc) = Just(4)
Nothing().map(R.inc) = Nothing()
How does it work
const Just = function (val) {
this.value = val
this.map = (fn) => {
return Just(fn(this.value))
}
}
const Nothing = function() {
this.value = null
this.map = (fn) => {
return this
}
}
import {Nothing, Just, fromMaybe} from 'sanctuary'
import {compose, findIndex, append, equals} from 'ramda'
const findByIndex = compose(i => i >= 0 ? Just(i) : Nothing(), findIndex)
const findAndReplace = (fn, replacement, array) => findIndex(fn, array)
.map(index => update(index, repacement, array))
const addOrReplace = (fn, replacement, array) => fromMaybe(
append(replacement, array),
findAndReplace(fn, replacment, array)
)
const equalsBar = equals('bar')
addOrReplace(equalsBar, 'quoox', ['foo', 'bar', 'baz']) // ['foo', 'quoox', 'baz']
addOrReplace(equalsBar, 'quoox', ['foo', 'baz']) // ['foo', 'baz', 'quoox']
Example
Links
FunctionalProgramming
By Mika Kalathil
FunctionalProgramming
Creating clean code with functional programming
- 1,017