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,011