Functional vs Imperative Patterns in JavaScript

JavaScript Joel - Oct 16 '18 - - Dev Community

The intention of this post is not to say one paradigm is better than the other. It is just to show common patterns you run into and their functional equivalents.

If there is a pattern I missed and you would like to see, post it in the comments below. Warning, boil your pattern down to the least common denominators. I can't convert a 100 line function! ;)

if / else

Imperative

const hour = 14
let greeting

if (hour < 18) {
  greeting = 'Good day';
} else {
  greeting = 'Good evening';
}
Enter fullscreen mode Exit fullscreen mode

Functional

The if can now be re-usable as function getGreeting.

A:

const isDay = hour => hour < 18
const getGreeting = hour => isDay(hour) ? 'Good Day' : 'Good Evening'
const greeting = getGreeting (hour)
Enter fullscreen mode Exit fullscreen mode

B:

import ifElse from 'mojiscript/logic/ifElse'

const isDay = hour => hour < 18
const getGreeting = ifElse (isDay) (() => 'Good Day') (() => 'Good evening')
const greeting = getGreeting (hour)
Enter fullscreen mode Exit fullscreen mode

if (no else)

Imperative

let name = 'joel'

if (name != null) {
  name = name.toUpperCase()
}
Enter fullscreen mode Exit fullscreen mode

Functional

isNotnull and toUpperCase are re-usable functions. name is not overwritten, instead upperName is created.

import when from 'mojiscript/logic/when'

const isNotNull = obj => obj != null
const toUpperCase = when (isNotNull) (string => string.toUpperCase ())

const name = 'joel'
const upperName = toUpperCase (name)
Enter fullscreen mode Exit fullscreen mode

Summing an Array

Imperative

const values = [1, 2, 3]

let sum = 0
for (const x of values) {
  sum = sum + x
}
Enter fullscreen mode Exit fullscreen mode

Functional

Do not mutate sum.

A:

const values = [1, 2, 3]

const add = (x, y) => x + y
const sum = values.reduce(add)
Enter fullscreen mode Exit fullscreen mode

B:

import reduce from 'mojiscript/list/reduce'

const add = x => y => x + y
const sum = reduce (add) (0)

const values = [1, 2, 3]
sum (values)
Enter fullscreen mode Exit fullscreen mode

for / if (1)

Imperative

const values = [1, 2, 3, 4, 5]

let evens = []
for (const x of values) {
  if (x % 2 === 0) {
    evens.push(x)
  }
}
Enter fullscreen mode Exit fullscreen mode

Functional

Do not mutate evens.

import filter from 'mojiscript/list/filter'

const values = [1, 2, 3, 4, 5]

const isEven = num => num % 2 === 0
const evens = filter (isEven) (values)
Enter fullscreen mode Exit fullscreen mode

for / if (2)

Imperative

const values = [1, 2, 3, 4, 5]

for (const x of values) {
  if (x % 2 === 0) {
    console.log(`${x} isEven`)
  }
}
Enter fullscreen mode Exit fullscreen mode

Functional

Use when for conditional execution.

import map from 'mojiscript/list/map'
import when from 'mojiscript/logic/when'

const isEven = num => num % 2 === 0
const logWhenEven = when (isEven) (x => console.log (`${x} isEven`))

const values = [1, 2, 3, 4, 5]
map (logWhenEven) (values)
Enter fullscreen mode Exit fullscreen mode

Breaking a loop early

Imperative

const values = [1, 2, 3]
let sum = 0
for (const x of values) {
  if (x > 3) break
  sum = sum + x 
}
Enter fullscreen mode Exit fullscreen mode

Functional

reduceWhile is like reduce, but accepts a predicate to "break" early.

import reduceWhile from 'mojiscript/list/reduceWhile'

const add = x => y => x + y
const lte3 = num => num <= 3

const sum = reduceWhile (() => lte3) (add) (0) (values)
Enter fullscreen mode Exit fullscreen mode

if / else if / else

Imperative

const fn = temp => {
   if (temp === 0) return 'water freezes at 0°C'
   else if (temp === 100) return 'water boils at 100°C'
   else return `nothing special happens at ${temp}°C`
}

fn(0) //=> 'water freezes at 0°C'
fn(50) //=> 'nothing special happens at 50°C'
fn(100) //=> 'water boils at 100°C'
Enter fullscreen mode Exit fullscreen mode

Functional

import cond from 'mojiscript/logic/cond'
import $ from 'mojiscript/string/template'

const fn = cond([
  [0, 'water freezes at 0°C'],
  [100, 'water boils at 100°C'],
  [() => true, $`nothing special happens at ${0}°C`]
])

fn(0) //=> 'water freezes at 0°C'
fn(50) //=> 'nothing special happens at 50°C'
fn(100) //=> 'water boils at 100°C'
Enter fullscreen mode Exit fullscreen mode

Setting properties

Imperative

const obj = {
  one: 1
}

obj.two = 2
Enter fullscreen mode Exit fullscreen mode

Functional

Do not mutate original object, shallow clone it and then add the new prop.

note: When objects are mutable, you must deep clone. If objects are immutable, you can shallow clone, which has obvious performance benefits.

const obj = {
  one: 1
}

const newObj = {
  ...obj,
  two: 2
}
Enter fullscreen mode Exit fullscreen mode

Modifying Arrays

Imperative

const values = [1, 2, 3]
values.push(4)
Enter fullscreen mode Exit fullscreen mode

Functional

Do not mutate values.

A:

const values = [1, 2, 3]
const newValues = [...values, 4]
Enter fullscreen mode Exit fullscreen mode

B:

For large arrays, use an immutable library like list for high performance immutable Arrays.

import L from 'list'

const values = L.from([1, 2, 3])
const newValues = L.append(4, values)
Enter fullscreen mode Exit fullscreen mode

Classes

Imperative

Prone to errors.

class Cat {
  constructor() {
    this.sound = 'Meow'
  }

  talk() {
    return this.sound
  }
}

const cat = new Cat()
const talk = cat.talk

cat.talk() //=> 'Meow'
talk() //=> Error: Cannot read property 'sound' of undefined
Enter fullscreen mode Exit fullscreen mode

Functional

Separation of function from data for maximum reusability.

const cat = {
  sound: 'Meow'
}

const dog = {
  sound: 'Woof'
}

const talk = animal => animal.sound

talk (cat) //=> 'Meow'
talk (dog) //=> 'Woof'
Enter fullscreen mode Exit fullscreen mode

Nested for loop

Imperative

let box = ''
for (let y = 0; y < 5; y++) {
  for (let x = 0; x < 5; x++) {
    box = box + '* '
  }
  box = box + '\n'
}
Enter fullscreen mode Exit fullscreen mode

Functional

No more nesting. Immutable.

import reduce from 'mojiscript/list/reduce'
import range from 'mojiscript/list/range'

const makeCols = cols =>
  reduce (acc => () => acc + '* ') ('') (range (0) (cols))

const makeBox = ({ cols, rows }) =>
  reduce (acc => () => `${acc}${makeCols (cols)}\n`) ('') (range (0) (rows))

const box = makeBox ({ cols: 5, rows: 5 })
//=> ​​​​​* * * * * ​​​​​
//=> ​​​​​* * * * * ​​​​​
//=> ​​​​​* * * * * ​​​​​
//=> ​​​​​* * * * * ​​​​​
//=> ​​​​​* * * * * ​​​​​
Enter fullscreen mode Exit fullscreen mode

And reusable!

const makeTriangle = length =>
  reduce
    (acc => i => `${acc}${' '.repeat(length - i)}${makeCols (i + 1)}\n`)
    ('')
    (range (0) (length))

const triangle = makeTriangle (5)
//=>​​​​​     * ​​​​​
//=>​​​​​    * * ​​​​​
//=>​​​​​   * * * ​​​​​
//=>​​​​​  * * * * ​​​​​
//=>​​​​​ * * * * * ​​​​​
Enter fullscreen mode Exit fullscreen mode

Null guard

Imperative

const toUpper = string => {
  if (string != null) {
    return string.toUpperCase()
  }
}
Enter fullscreen mode Exit fullscreen mode

Functional

A:

This example wraps the argument in a Maybe type and then unwraps it at the end. In a typical FP app, you would be using the Maybe throughout your app, so you would not need to wrap and unwrap the string. So this is a little more verbose than you would normally see.

import S from 'sanctuary'

const toUpper = S.pipe ([
  S.toMaybe,
  S.map (string => string.toUpperCase ()),
  S.maybeToNullable
])

// If you use `Maybe` throughout your app, this would be your `toUpper` function.
const toUpper = S.map (string => string.toUpperCase ())
Enter fullscreen mode Exit fullscreen mode

B:

maybe is a function decorator that executes the function only if an argument is supplied. Now our null guard is reusable. More on function decorators here: Functional JavaScript: Function Decorators Part 2 #JavaScript

const maybe = func => (...args) =>
  args.length === 0 || args[0] == null
    ? args[0]
    : func(...args)

const toUpper = maybe(string => string.toUpperCase ())
Enter fullscreen mode Exit fullscreen mode

End

My articles are very Functional JavaScript heavy, if you need more FP, follow me here, or on Twitter @joelnet!

More articles
Ask me dumb questions about functional programming
Let's make a DEV.to CLI... together
Let's talk about auto-generated documentation tools for JavaScript

Cheers!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .